#!/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 """ ### sur le dialog (1.1) de zamok, il manque les fonctionnalité suivante présente dans la 1.2 ### --default-button pour choisir le bouton sélectionner par defaut ### --not-tags pour masquer les tags quand il ne servent à rien pour l'utilisateur (mais sont utilisé comme index par le programme) import os import sys import time import ldap import signal import inspect import tempfile import collections sys.path.append('/usr/scripts/') from pythondialog import Dialog from gestion.affich_tools import get_screen_size, coul import gestion.config as config import lc_ldap.shortcuts import lc_ldap.objets import lc_ldap.attributs as attributs import lc_ldap.printing as printing debugf=None debug_enable = False def mydebug(txt): global debugf, debug_enable if debug_enable: if debugf is None: debugf = open('/tmp/gest_crans_lc.log', 'w') if isinstance(txt, list): for v in txt: mydebug(' ' + str(v)) else: debugf.write(str(txt)+'\n') debugf.flush() os.fsync(debugf) # Implémentation "à la main" de la tail récursion en python # voir http://kylem.net/programming/tailcall.html # je trouve ça assez sioux # En plus, ça nous permet de gérer plus facilement le # code en CPS class TailCaller(object) : """ Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion faite "à la main" en python (voir http://kylem.net/programming/tailcall.html) """ other_callers = {} def __init__(self, f) : self.f = f self.func_name = f.func_name TailCaller.other_callers[id(self)]=f.func_name def __del__(self): del(TailCaller.other_callers[id(self)]) def __call__(self, *args, **kwargs) : mydebug("***%s calling" % self.func_name) stacklvl = len(inspect.stack()) try: ret = self.f(*args, **kwargs) except Continue as c: ret = c.tailCall while isinstance(ret, TailCall): # Si la continuation a été créer par un TailCaller précédent, on propage # Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall # Crée après lui. if stacklvl>=ret.stacklvl: mydebug("***%s terminate" % self.func_name) raise Continue(ret) mydebug("***%s doing %s" % (self.func_name, ret)) try: ret = ret.handle() except Continue as c: ret = c.tailCall mydebug("***%s returning %s" % (self.func_name, ret)) return ret def tailcaller(f): """ Décorateur retardant la décoration par TailCaller d'une fonction À utiliser sur les fonctions faisant de la tail récursion et devant retourner une valeur. On l'utilise de toute façon sur la fonction d'entrée dans l'application Une fonction de devrait pas instancier de TailCall sans être décoré par ce décorteur de façon générale """ f.tailCaller = True return f class Continue(Exception): """Exception pour envoyer des TailCall en les raisant""" def __init__(self, tailCall): self.tailCall = tailCall class TailCall(object) : """ Appel tail récursif à une fonction et ses argument On peut voir un TailCall comme le fait de retarder l'appel à une fonction (avec ses listes d'arguements) à un moment futur. La fonction sera appelée dans les cas suivant : * Le TailCall est retourné par une fonction qui est un TailCaller * L'exception Continue(TailCall(...)) est levée et traverse une fonction qui est un TailCaller """ def __init__(self, call, *args, **kwargs) : self.stacklvl = len(inspect.stack()) if isinstance(call, TailCall): call.kwargs.update(**kwargs) kwargs = call.kwargs args = call.args + args call = call.call self.call = call self.args = args self.kwargs = kwargs def __str__(self): return "TailCall<%s(%s%s%s)>" % (self.call.func_name, ', '.join(repr(a) for a in self.args), ', ' if self.args and self.kwargs else '', ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items())) def __call__(self, *args, **kwargs): self.kwargs.update(kwargs) self.args = self.args + args return self def handle(self) : caller = None call = self.call while isinstance(call, TailCaller) : caller = call call = self.call.f return call(*self.args, **self.kwargs) def unicode_of_Error(x): """Formatte l'exception de type ValueError""" return u"\n".join(unicode(i, 'utf-8') if type(i) == str else repr(i) for i in x.args) def handle_exit_code(d, code): """Gère les codes de retour dialog du menu principal""" 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): """Interface de gestion des machines et des adhérents du crans, version lc_ldap""" def __getattribute__(self, attr): ret = super(GestCrans, self).__getattribute__(attr) # Petit hack pour que ça soit les methodes de l'objet instancié qui soient # décorée par TailCaller et pas les méthodes statiques de la classe GestCrans if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller): ret = TailCaller(ret) setattr(self, attr, ret) return ret def __init__(self): if not debug_enable: 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() @tailcaller def edit_blacklist_select(self, obj, title, cont): """ Permet de choisir une blackliste parmis celle de obj oubien une nouvelle blacklist. Retourne (index, bl) où bl est un dictionnaire représentant la blackliste et index l'index de la blackliste dans obj['blacklist'] ou new pour une nouvelle blacklist """ def box(): choices = [('new', 'Ajouter une nouvelle blackliste')] index = 0 for bl in obj['blacklist']: choices.append((str(index), coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None, dialog=True))) index+=1 return self.dialog.menu( "Éditer une blacklist ou en ajouter une nouvelle ?\n(les blacklistes actives apparaissent en rouge)", width=0, height=0, menu_height=0, item_help=0, title=title, scrollbar=True, colors=True, cancel_label="Retour", backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=choices) (code, tag) = box() retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) elif code in [self.dialog.DIALOG_OK]: if tag == 'new': return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''} else: try: bl = {} bl.update(obj['blacklist'][int(tag)].value) return tag, bl except Exception as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont(values=values)) else: self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont) @tailcaller def edit_blacklist_type(self, title, cont): """Permet de choisir un type de blackliste pour les nouvelles blacklistes""" def box(): return self.dialog.menu( "Choisissez un type de blacklist", width=0, height=0, menu_height=0, item_help=0, title=title, scrollbar=True, colors=True, cancel_label="Retour", backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=[(k,v) for (k,v) in config.blacklist_items.items()]) (code, tag) = box() retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) elif code in [self.dialog.DIALOG_OK]: if tag in config.blacklist_items: return tag else: self.dialog.msgbox("%s n'est pas une blacklist" % tag, title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont(values=values)) else: self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont) @tailcaller def get_timestamp(self, title, text, cont, hour=-1, minute=-1, second=-1, day=0, month=0, year=0): """Fait choisir une date et une heure et retourne le tuple (year, month, day, hour, minute, second)""" retry_cont = TailCall(self.get_timestamp, title=title, text=text, cont=cont, hour=hour, minute=minute, second=second, day=day, month=month, year=year) def get_date(day, month, year): (code, output) = self.dialog.calendar(text, day=day, month=month, year=year, title=title) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) elif output: (day, month, year) = output return (year, month, day) else: raise EnvironmentError("Pourquoi je n'ai pas de date ?") def get_time(hour, minute, second, day, month, year): (code, output) = self.dialog.timebox(text, hour=hour, minute=minute, second=second) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(retry_cont(day=day, month=month, year=year)) elif output: (hour, minute, second) = output return (hour, minute, second) else: raise EnvironmentError("Pourquoi je n'ai pas d'horaire ?") (year, month, day) = get_date(day, month, year) (hour, minute, second) = get_time(hour, minute, second, day, month, year) return (year, month, day) + (hour, minute, second) def edit_blacklist(self, obj, title, update_obj, cont, bl=None, tag=None, bl_type=None, debut=None, fin=None, comm=None): """Pour éditer les blacklistes d'un objet lc_ldap""" self_cont = TailCall(self.edit_blacklist, obj=obj, title=title, update_obj=update_obj, cont=cont, bl=bl, tag=tag, bl_type=bl_type, debut=debut, fin=fin, comm=comm) # Si bl ou tag ne sont pas définit on les demande if bl is None or tag is None: bl_type = None debut = None fin = None comm = None tag, bl = self.edit_blacklist_select(obj, title, cont) if bl_type is None and tag == 'new': bl['type'] = self.edit_blacklist_type(title, self_cont) elif tag == 'new': bl['type'] = bl_type # Cas de l'ajout d'un blacklist if tag == 'new': # Si debut n'est pas encore spécifié, on le demande if debut is None: debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, bl_type=None, debut=None, fin=None, comm=None)) debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) # Idem pour fin if fin is None: if self.dialog.yesno("Mettre une date de fin ?", title=title) == self.dialog.DIALOG_OK: fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, bl_type=bl_type, debut=None, fin=None, comm=None)) fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) else: fin = '-' bl['debut']=debut bl['fin']=fin bl['comm']=self.get_comment(title=title, text="Commentaire ?", cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Ajouter la blacklist ?"): try: with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: obj['blacklist'].append(bl) obj.save() # On s'en va en mettant à jour dans la continuation la valeur de obj raise Continue(cont(**{update_obj:obj})) # On propage les Continue except Continue: raise # En cas d'une autre erreur, on l'affiche et on retourne except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73) raise Continue(self_cont) else: raise Continue(self_cont(bl=None)) # Cas de l'édition d'une blacklist else: if debut is None: # Mettre un warning pour éditer (seulement quand debut vaut None pour ne pas le répéter à chaque fois que l'on revient en arrière par la suite if not self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Éditer la blackliste ?"): raise Continue(self_cont(bl=None)) debut = time.localtime(bl['debut']) debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, debut=None, fin=None, comm=None), day=debut.tm_mday, month=debut.tm_mon, year=debut.tm_year, hour=debut.tm_hour, minute=debut.tm_min, second=debut.tm_sec ) debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) bl['debut'] = debut if fin is None: if self.dialog.yesno("Mettre une date de fin ?", title=title) == self.dialog.DIALOG_OK: if bl['fin'] == '-': fin = time.localtime() else: fin = time.localtime(bl['fin']) fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, debut=debut, fin=None, comm=None), day=fin.tm_mday, month=fin.tm_mon, year=fin.tm_year, hour=fin.tm_hour, minute=fin.tm_min, second=fin.tm_sec ) fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) else: fin = '-' bl['fin'] = fin bl['comm']=self.get_comment(title=title, text="Commentaire ?", init=bl['comm'], cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Modifier la blacklist ?"): try: with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: obj['blacklist'][int(tag)]=bl obj.save() # On s'en va en mettant à jour dans la continuation la valeur de obj raise Continue(cont(**{update_obj:obj})) # On propage les Continue except Continue: raise # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73) raise Continue(self_cont) else: raise Continue(self_cont(bl=None)) def edit_boolean_attributs(self, obj, attribs, title, update_obj, cont, values={}): """ Permet d'éditer des attributs booléen de l'objet obj listé dans attribs. update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié """ # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut missing = { 'default' : False, # par défaut, on dit que c'est False attributs.dnsIpv6 : True # pour dnsIpv6, c'est True } choices = [(a.ldap_name, a.legend, 1 if values.get(a.ldap_name, obj[a.ldap_name][0] if obj[a.ldap_name] else missing.get(a, missing['default'])) else 0) for a in attribs] (code, output) = self.dialog.checklist("Activier ou désactiver les propriétés suivantes", height=0, width=0, list_height=7, choices=choices, title=title) # Une continuation que l'on suivra si quelque chose se passe mal retry_cont = TailCall(self.edit_boolean_attributs, obj=obj, update_obj=update_obj, attribs=attribs, title=title, cont=cont, values=values) # Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) # Sinon, c'est OK elif code in [self.dialog.DIALOG_OK]: # On transforme la liste des cases dialog cochée en dictionnnaire values = dict((a.ldap_name, a.ldap_name in output) for a in attribs) try: # On met à jour chaque attribut si sa valeur à changé with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: for a in attribs: if obj[a.ldap_name] and obj[a.ldap_name][0] != values[a.ldap_name]: obj[a.ldap_name]=values[a.ldap_name] elif not obj[a.ldap_name] and missing.get(a, missing['default']) != values[a.ldap_name]: obj[a.ldap_name]=values[a.ldap_name] obj.save() # On s'en va en mettant à jour dans la continuation la valeur de obj raise Continue(cont(**{update_obj:obj})) # On propage les Continue except Continue: raise # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont(values=values)) else: self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont) def edit_attributs(self, obj, attr, title, update_obj, cont, tag=None, values=None): """ Permet d'éditer la liste d'attribut attr de l'objet obj. update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié """ # Il n'y a pas inputmenu dans la lib dialog, du coup, on traite les arguments à la main. # Ça reste relativement acceptable comme on utilise la fonction de la lib pour appeler dialog def box(values, default_tag): cmd = ['--inputmenu', "Édition de l'attribut %s :" % attr, "0", "0", "20"] index=0 for value in values: cmd.extend([str(index), str(value)]) index+=1 cmd.extend(['new', '']) (code, output) = self.dialog._perform(*(cmd,), title=title, default_item=str(default_tag)) if code == self.dialog.DIALOG_EXTRA: output = output.split(' ', 2)[1:] else: output = '' return (code, output) if values is None: values = [str(a) for a in obj[attr]] retry_cont = TailCall(self.edit_attributs, obj=obj, attr=attr, title=title, update_obj=update_obj, cont=cont, values=values) (code, output) = box(values, tag) # Si on a fait annulé ou appuyé sur ESC if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) # Si on a éditer ou ajouté une valeur de/dans la liste de l'attribut elif code in [self.dialog.DIALOG_EXTRA]: tag, value = output if tag == 'new': if value: values.append(value) elif value == '': values.pop(int(tag)) else: values[int(tag)] = value raise Continue(retry_cont(values=values, tag=tag)) # Si on valide les changement affectué, on sauvegarde les nouvelles valeurs dans l'objet elif code in [self.dialog.DIALOG_OK]: try: with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: obj[attr] = [unicode(value, 'utf-8') for value in values] obj.save() raise Continue(cont(**{update_obj:obj})) except Continue: raise except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont) # Si dialog retourne un code inconnu, on le signale else: self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont) 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): raise Continue(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'] ] 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): raise Continue(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) raise Continue(TailCall(self.select_one, items=items, title=title, default_item=default_item, cont=cont)) # Sinon on retourne l'item choisis elif self.confirm_item(items[int(tag)], title): return items[int(tag)] else: raise Continue(cont) @tailcaller def get_comment(self, title, text, cont, init='', force=False): """ Fait entrer à l'utilisateur un commentaire et le retourne. Si force est à True, on oblige le commentaire à être non vide """ (code, output) = self.dialog.inputbox(text=text, title=title, init=init) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) elif force and not output: self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose") raise Continue(TailCall(self.get_comment, title=title, text=text, cont=cont, force=force)) else: return unicode(output, 'utf-8') def confirm_item(self, item, title, defaultno=False, **params): """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, **params), no_collapse=True, colors=True, title=title, defaultno=defaultno, width=0, height=0, backtitle="Appuyez sur MAJ pour selectionner du texte" ) == self.dialog.DIALOG_OK def display_item(self, item, title, **params): """Affiche un item""" return self.dialog.msgbox( printing.sprint(item, **params), no_collapse=True, colors=True, title=title, width=0, height=0, backtitle="Appuyez sur MAJ pour selectionner du texte" ) # On a besoin du décorateur ici car select va retourner un item après avoir # possblement traiter plusieurs tailcall @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) raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=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=objectClassS, title=title, values=values, cont=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.confirm_item(item, title): return item else: raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) except Continue: raise except Exception as e: self.dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0) raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) def machine_information(self, cont, machine=None, objectClass=None, parent=None, realm=None, fields_values=None): """ Permet de modifier une machine si elle est fournit par le paramètre machine sinon, crée une machine à partir de parent, objectClass et realm. Si on ne fait qu'éditer une machine, parent, objectClass et realm sont ignoré D'une machinère générale, il faudrait mettre ici tous les attributs single value et les multivalué que l'on peut simplement représenter de façon textuelle avec un séparateur. Pour le moment il y a : * host * macAddress * ipHostNumber * port(TCP|UDP)(in|out) """ a = attributs # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15), (a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50), (a.portUDPin, 50) ] # Quel séparateur on utilise pour les champs multivalué separateur = ' ' if machine: objectClass = machine["objectClass"][0] attrs = dict((k,[str(a) for a in at]) for k,at in machine.items()) else: attrs = {} fields = [("%s :" % a.legend, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else [])), l+1, l) for a,l in to_display] (code, tags) = self.dialog.form( text="", height=0, width=0, form_height=0, fields=fields_values if fields_values else fields, title="Paramètres machine", backtitle="Gestion des machines du Crans") if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré # c'est au cableur de corriger ou d'annuler fields_values = [] for ((a,l),values) in zip(to_display, tags): fields_values.append(("%s :" % a.legend, values, l)) try: attrs = {} # On traite les valeurs reçues for ((a,l),values) in zip(to_display, tags): values = unicode(values, 'utf-8') # Si le champs n'est pas single value, on utilise separateur pour découper # et on ne garde que les valeurs non vides if not a.singlevalue: values = [v for v in values.split(separateur) if v] # Pour host, on fait quelqes vérification de syntaxe if a.ldap_name == 'host': # Si c'est une machine wifi, host doit finir par wifi.crans.org if "machineWifi" == objectClass: hostend = ".wifi.crans.org" # Si c'est une machine wifi, host doit finir par crans.org elif "machineFixe" == objectClass: hostend = ".crans.org" # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer # plus finement fonction des droits de self.conn.droits else: raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass) if not values.endswith(hostend) and not '.' in values: values = "%s.wifi.crans.org" % values elif values.endswith(hostend) and '.' in values[:-len(hostend)]: raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) elif not values.endswith(hostend) and '.' in values: raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) attrs[a.ldap_name]=values if machine: with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: for (key, values) in attrs.items(): machine[key]=values machine.validate_changes() machine.save() else: if parent is None or realm is None: raise EnvironmentError("On essaye de créer une machine mais parent ou realm vaut None") ldif = { 'macAddress': ['%s' % attrs['macAddress'].encode('utf-8')], 'host': ['%s' % attrs['host'].encode('utf-8')] } with self.conn.newMachine(parent.dn, realm, ldif) as machine: for (key, values) in attrs.items(): machine[key]=values if attributs.ipsec in machine.attribs: machine[attributs.ipsec.ldap_name]=attributs.ipsec.default machine.validate_changes() if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"): machine.create() self.display_item(machine, "La machine à bien été créée", ipsec=True) else: raise Continue(cont) return cont(machine=machine) except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) raise Continue(TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, parent=parent, realm=realm, fields_values=fields_values)) def modif_machine_blacklist(self, machine, cont): """Raccourci vers edit_blacklist spécifique aux machines""" return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont) def certificat_tlsa(self, certificat, cont, values=None): """Menu d'éditions des paramètres TLSA d'un certificat""" separateur = ' ' form = { 'Type de certificat' : {'ldap_name' : 'certificatUsage', 'text':"".join(str(s) for s in certificat.get('certificatUsage', [])), 'len':1}, 'Type de correspondance' : {'ldap_name' : 'matchingType', 'text':"".join(str(s) for s in certificat.get('matchingType', [])), 'len':1}, 'Ports TCP' : {'ldap_name' : 'portTCPin', 'text':separateur.join(str(p) for p in certificat.get('portTCPin', [])), 'len':30}, 'Ports UDP' : {'ldap_name' : 'portUDPin', 'text':separateur.join(str(p) for p in certificat.get('portUDPin', [])), 'len':30}, } form_order = ['Type de certificat', 'Type de correspondance', 'Ports TCP', 'Ports UDP'] def box(fields_values=None): fields = [("%s : " % k, form[k]['text'], form[k]['len'] + 1, form[k]['len']) for k in form_order] return self.dialog.form( text="", height=0, width=0, form_height=0, fields=fields_values if fields_values else fields, title="Paramètres TLS d'un certificat de la machine %s" % certificat.machine()['host'][0], backtitle="Gestion des certificats des machines du Crans") (code, output) = box(values) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) else: values = dict(zip([form[k]['ldap_name'] for k in form_order], output)) fields_values = [("%s : " % k, values[form[k]['ldap_name']], form[k]['len'] + 1, form[k]['len']) for k in form_order] self_cont=TailCall(self.certificat_tlsa, certificat=certificat, cont=cont, values=fields_values) try: if not values['certificatUsage'] in ['0', '1', '2', '3']: raise ValueError("""Type de certificat invalide : les valeurs valident sont : * 0 pour du CA pinning (le certificat doit être une autorité de certification valide) * 1 pour du certificat pinning (le certificat doit déjà être validé par les navigateur) * 2 pour ajouter un CA (pour les autorité de certification autosigné) * 3 pour les certificats autosigné""" ) if not values['matchingType'] in ['0', '1', '2']: raise ValueError("""Type de correspondance invalide : les valeurs valident sont : * 0 le certificat sera mis entièrement dans le dns * 1 le sha256 du certificat sera mis dans le dns * 2 le sha512 du certificat sera mis dans le dns""" ) with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: if "TLSACert" in certificat['objectClass']: certificat['certificatUsage'] = unicode(values['certificatUsage']) certificat['matchingType'] = unicode(values['matchingType']) else: certificat.tlsa(values['certificatUsage'], values['matchingType']) certificat['portTCPin'] = [unicode(s, 'utf-8') for s in values['portTCPin'].split(separateur) if s] certificat['portUDPin'] = [unicode(s, 'utf-8') for s in values['portUDPin'].split(separateur) if s] certificat.save() raise Continue(cont(certificat=certificat)) except Continue: raise except Exception as e: self.dialog.msgbox("%s" % (unicode_of_Error(e)), title="Erreur rencontrée", width=73, height=10) raise Continue(self_cont) def create_certificat(self, machine, cont): """Permet d'ajouter un certificat à une machine à partir du PEM du certificat""" # input multiline en utilisant un editbox def box(): fp, path = tempfile.mkstemp() os.close(fp) cmd = ['--editbox', path, "0", "0"] (code, output) = self.dialog._perform(*(cmd,), no_mouse=True, backtitle="Appuyez sur CTRL+MAJ+V pour coller", title="Création d'un certificat, entrez le PEM du certificat") os.remove(path) if code == self.dialog.DIALOG_OK: return code, output else: return code, None (code, pem) = box() if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) else: try: with self.conn.newCertificat(machine.dn, {}) as certificat: certificat['certificat'] = unicode(pem, 'utf-8') certificat.create() raise Continue(cont(certificat=certificat, machine=certificat.machine())) except Continue: raise except Exception as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) self_cont = TailCall(self.create_certificat, machine=machine, cont=cont) raise Continue(self_cont) def delete_certificat(self, certificat, cont): """Supprime un certificat""" if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"): try: with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: certificat.delete() self.dialog.msgbox("Le certificat a bien été supprimé", title="Suppression d'un certificat") raise Continue(cont(certificat=None, machine=certificat.machine(refresh=True))) except Continue: raise except Exception as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) raise Continue(cont) else: raise Continue(cont(certificat=certificat)) def modif_machine_certificat(self, machine, cont, tag=None, certificat=None): """Permet l'édition d'un certificat d'une machine""" self_cont = TailCall(self.modif_machine_certificat, machine=machine, cont=cont, certificat=certificat) if certificat is None: certificat_index = self.edit_certificat_select(machine=machine, title="Modification des certificats de %s" % machine['host'][0], cont=cont) if certificat_index == 'new': raise Continue(TailCall(self.create_certificat, machine=machine, cont=self_cont)) certificat = machine.certificats()[certificat_index] menu = { 'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "attribut":attributs.hostCert}, 'TLSA' : {'text':"Paramètres pour les champs dns TLSA", "callback":self.certificat_tlsa}, 'Autre': {'text' : "Modifier les attribut booléen comme revocked", "callback":self.modif_certificat_boolean}, 'Supprimer' : {'text' : "Supprimer le certificat", "callback":self.delete_certificat}, } menu_order = ['Hostname', 'TLSA', 'Autre', 'Supprimer'] def box(default_item=None): return self.dialog.menu( "Paramètres pour le certificat N°0x%X émis par %s, valable du %s au %s" % ( int(str(certificat['serialNumber'][0])), certificat['issuerCN'][0], time.strftime("%d/%m/%Y", time.localtime(int(certificat['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(certificat['end'][0]))) ), width=0, height=0, menu_height=0, item_help=0, default_item=str(default_item), title="Modification des certificats de %s" % certificat.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): if certificat is None: raise Continue(cont(machine=machine)) else: raise Continue(self_cont(certificat=None)) elif not tag in menu_order: raise Continue(self_cont(certificat=certificat)) else: if 'callback' in menu[tag]: raise Continue(TailCall(menu[tag]['callback'], certificat=certificat, cont=self_cont(certificat=certificat, tag=tag))) elif 'attribut' in menu[tag]: raise Continue(TailCall(self.modif_certificat_attributs, certificat=certificat, cont=self_cont(certificat=certificat, tag=tag), attr=menu[tag]['attribut'].ldap_name)) else: raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) @tailcaller def edit_certificat_select(self, machine, title, cont): """Permet de choisir un certificat existant ou nouveau d'une machine""" def box(default_item=None): index=0 choices = [('new', 'Ajouter un nouveau certificat')] for cert in machine.certificats(): choices.append((str(index), "Emit par %s pour %s du %s au %s" % (cert['issuerCN'][0], ', '.join(str(cn) for cn in cert['hostCert']), time.strftime("%d/%m/%Y", time.localtime(int(cert['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(cert['end'][0])))))) index+=1 return self.dialog.menu( "Modifier ou ajouter un certificat ?", width=0, height=0, menu_height=0, item_help=0, title="Modification des certificats de %s" % machine['host'][0], scrollbar=True, default_item=str(default_item), cancel_label="Retour", backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=choices) (code, tag) = box() retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): raise Continue(cont) elif code in [self.dialog.DIALOG_OK]: if tag == 'new': return tag else: return int(tag) else: self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) raise Continue(retry_cont) return cont def modif_machine_attributs(self, machine, attr, cont): """Juste un raccourci vers edit_attributs spécifique aux machines""" return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont) def modif_certificat_attributs(self, certificat, attr, cont): """Juste un raccourci vers edit_attributs spécifique aux certificats""" return self.edit_attributs(obj=certificat, update_obj='certificat', attr=attr, title="Modification d'un certificat de la machine %s" % certificat.machine()['host'][0], cont=cont) def modif_machine_boolean(self, machine, cont): """Juste un raccourci vers edit_boolean_attributs spécifique aux machines""" a = attributs attribs = [a.dnsIpv6] return self.edit_boolean_attributs( obj=machine, attribs=attribs, title="Édition des attributs booléen de la machine %s" % machine['host'][0], update_obj='machine', cont=cont) def modif_certificat_boolean(self, certificat, cont): """Juste un raccourci vers edit_boolean_attributs spécifique aux certificats""" a = attributs attribs = [a.revocked] return self.edit_boolean_attributs( obj=certificat, attribs=attribs, title="Édition des attributs booléen d'un certificat de la machine %s" % certificat.machine()['host'][0], update_obj='certificat', cont=cont) def modif_machine(self, cont, machine=None, tag=None): """ Permet d'éditer une machine. Si fournie en paramètre on éditer en place, sinon, on en cherche une dans la base ldap """ if machine is None: machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont) a = attributs menu = { 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information}, 'Autre' : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean}, 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist}, 'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat}, 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt}, 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias}, 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info}, 'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint}, } menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', '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): raise Continue(cont(machine=machine)) elif not tag in menu_order: raise Continue(TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)) else: cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) if 'callback' in menu[tag]: raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret)) elif 'attribut' in menu[tag]: raise Continue(TailCall(self.modif_machine_attributs, machine=machine, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) else: raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) def create_machine_adherent(self, cont, adherent=None, tag=None): """ Permet d'ajouter une machine à un adhérent. On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment) """ if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont) menu = { 'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'}, 'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'}, } menu_order = ['Fixe', 'Wifi'] def box(default_item=None): return self.dialog.menu( "Type de Machine ?", width=0, height=0, menu_height=0, item_help=0, default_item=str(default_item), title="Création de machines", 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): raise Continue(cont(proprio=adherent)) elif not tag in menu_order: raise Continue(TailCall(self.create_machine_adherent, cont=cont, adherent=adherent, tag=tag)) else: return self.machine_information( cont=cont(proprio=adherent), machine=None, objectClass=menu[tag]['objectClass'], parent=adherent, realm=menu[tag]['realm'] ) def delete_machine(self, cont, machine=None): """Permet la suppression d'une machine de la base ldap""" if machine is None: machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont) if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True): try: with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: machine.delete() self.dialog.msgbox("La machine a bien été supprimée", title="Suppression d'une machine") raise Continue(cont(machine=None)) except Continue: raise except Exception as e: self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) raise Continue(cont) else: raise Continue(cont) return cont 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) def create_adherent(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont 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 create_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def delete_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def modif_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def create_machine_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def create_machine_crans(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont 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 principal de l'application affiché au lancement""" 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=tag, machine=machine, proprio=proprio)) else: return TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine) if __name__ == '__main__': GestCrans().menu_principal() os.system('clear')