diff --git a/doc/aclmanager.html b/doc/aclmanager.html new file mode 100644 index 0000000..64d04c9 --- /dev/null +++ b/doc/aclmanager.html @@ -0,0 +1,351 @@ + + +aclmanager + + + +

+aclmanager

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + + + + +
AclManagerHauptfenster des ACL-Managers.
IpAclManagerVerwaltung fuer IP Adressen und deren ACL Level.
+

+Functions

+ + +
None
+

+ +

AclManager

+

+Hauptfenster des ACL-Managers. +

+

+Derived from

+ttk.Frame +

+Class Attributes

+ + +
acl
acltext
root
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AclManagerInit RevPiOption-Class.
__get_acltextGetter fuer Leveltexte.
__set_acltextSetter fuer Leveltexte.
_ask_deleteLöscht ein Eintrag der Liste.
_changesdonePrüft ob sich die Einstellungen geändert haben.
_checkclosePrüft ob Fenster beendet werden soll.
_clearfieldsLeert die Eingabefelder.
_createwidgetsErstellt Widgets.
_loadfieldsÜbernimmt Listeneintrag in Editfelder.
_refreshaclsLeert die ACL Liste und füllt sie neu.
_saveÜbernimt die Änderungen.
_savefieldsÜbernimmt neuen ACL Eintrag.
_status_editremoveSetzt state der Buttons.
get_aclGibt die Konfigurierten ACL zurück.
+

+Static Methods

+ + +
None
+ +

+AclManager (Constructor)

+AclManager(master, minlevel, maxlevel, acl_str="", readonly=False) +

+Init RevPiOption-Class. +

+
Returns:
+
+None +
+
+

+AclManager.__get_acltext

+__get_acltext() +

+Getter fuer Leveltexte. +

+
Returns:
+
+Leveltexte als +
+
+

+AclManager.__set_acltext

+__set_acltext(value) +

+Setter fuer Leveltexte. +

+
value
+
+Leveltexte als +
+
+

+AclManager._ask_delete

+_ask_delete() +

+Löscht ein Eintrag der Liste. +

+

+AclManager._changesdone

+_changesdone() +

+Prüft ob sich die Einstellungen geändert haben. +

+
Returns:
+
+True, wenn min. eine Einstellung geändert wurde +
+
+

+AclManager._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+AclManager._clearfields

+_clearfields() +

+Leert die Eingabefelder. +

+

+AclManager._createwidgets

+_createwidgets() +

+Erstellt Widgets. +

+

+AclManager._loadfields

+_loadfields() +

+Übernimmt Listeneintrag in Editfelder. +

+

+AclManager._refreshacls

+_refreshacls() +

+Leert die ACL Liste und füllt sie neu. +

+

+AclManager._save

+_save() +

+Übernimt die Änderungen. +

+

+AclManager._savefields

+_savefields() +

+Übernimmt neuen ACL Eintrag. +

+

+AclManager._status_editremove

+_status_editremove(tkevt) +

+Setzt state der Buttons. +

+

+AclManager.get_acl

+get_acl() +

+Gibt die Konfigurierten ACL zurück. +

+
Returns:
+
+ACL als +
+
+
Up
+

+ +

IpAclManager

+

+Verwaltung fuer IP Adressen und deren ACL Level. +

+

+Derived from

+None +

+Class Attributes

+ + +
acl
regex_acl
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + +
IpAclManagerInit IpAclManager class.
__get_aclGetter fuer den rohen ACL-String.
__get_regex_aclGibt formatierten RegEx-String zurueck.
__iter__Gibt einzelne ACLs als aus.
__set_aclUebernimmt neue ACL-Liste fuer die Ausertung der Level.
get_acllevelPrueft IP gegen ACL List und gibt ACL-Wert aus.
loadaclLaed ACL String und gibt erfolg zurueck.
+

+Static Methods

+ + +
None
+ +

+IpAclManager (Constructor)

+IpAclManager(minlevel, maxlevel, acl=None) +

+Init IpAclManager class. +

+
minlevel
+
+Smallest access level (min. 0) +
maxlevel
+
+Biggest access level (max. 9) +
acl
+
+ACL Liste fuer Berechtigungen als +
+
+

+IpAclManager.__get_acl

+__get_acl() +

+Getter fuer den rohen ACL-String. + return ACLs als +

+

+IpAclManager.__get_regex_acl

+__get_regex_acl() +

+Gibt formatierten RegEx-String zurueck. + return RegEx Code als +

+

+IpAclManager.__iter__

+__iter__() +

+Gibt einzelne ACLs als aus. +

+

+IpAclManager.__set_acl

+__set_acl(value) +

+Uebernimmt neue ACL-Liste fuer die Ausertung der Level. +

+
value
+
+Neue ACL-Liste als +
+
+

+IpAclManager.get_acllevel

+get_acllevel(ipaddress) +

+Prueft IP gegen ACL List und gibt ACL-Wert aus. +

+
ipaddress
+
+zum pruefen +
+
+
Returns:
+
+ ACL Wert oder -1 wenn nicht gefunden +
+
+

+IpAclManager.loadacl

+loadacl(str_acl) +

+Laed ACL String und gibt erfolg zurueck. +

+
str_acl
+
+ACL als +
+
+
Returns:
+
+True, wenn erfolgreich uebernommen +
+
+
Up
+
+ \ No newline at end of file diff --git a/doc/index.html b/doc/index.html index 621e3bb..7b328d6 100644 --- a/doc/index.html +++ b/doc/index.html @@ -13,9 +13,12 @@ Table of contents Modules - + + + + @@ -28,6 +31,9 @@ Modules + + + diff --git a/doc/mytools.html b/doc/mytools.html index c8a2a96..fc798c4 100644 --- a/doc/mytools.html +++ b/doc/mytools.html @@ -6,7 +6,9 @@

mytools

- +

+Tools-Sammlung. +

Global Attributes

mytoolsaclmanager
mytoolsTools-Sammlung.
revpicheckclient
revpioption
revpioptionlegacy
revpiplclist
@@ -25,7 +27,7 @@ Functions - +
Hängt root-dir der Anwendung vor Dateinamen.
gettransWertet die Sprache des OS aus und gibt Übersetzung zurück.


@@ -53,7 +55,19 @@ root dir

gettrans

gettrans(proglang=None) - +

+Wertet die Sprache des OS aus und gibt Übersetzung zurück. +

+
proglang
+
+Bestimmte Sprache laden +
+
+
Returns:
+
+gettext Übersetzung für Zuweisung an '_' +
+
Up

\ No newline at end of file diff --git a/doc/revpioption.html b/doc/revpioption.html index 1c42b8d..2e2fd97 100644 --- a/doc/revpioption.html +++ b/doc/revpioption.html @@ -67,11 +67,11 @@ Methods askxmlon Fragt Nuter, ob wirklicht abgeschaltet werden soll. -xmlmod2_tail -Passt XML-Optionszugriff an. +btn_slaveacl +Öffnet Fenster für ACL-Verwaltung. -xmlmod_tail -Passt XML-Optionszugriff an. +btn_xmlacl +Öffnet Fenster für ACL-Verwaltung.

@@ -146,18 +146,18 @@ RevPiOption.askxmlon

askxmlon()

Fragt Nuter, ob wirklicht abgeschaltet werden soll. -

+

-RevPiOption.xmlmod2_tail

-xmlmod2_tail() +RevPiOption.btn_slaveacl +btn_slaveacl()

-Passt XML-Optionszugriff an. -

+Öffnet Fenster für ACL-Verwaltung. +

-RevPiOption.xmlmod_tail

-xmlmod_tail() +RevPiOption.btn_xmlacl +btn_xmlacl()

-Passt XML-Optionszugriff an. +Öffnet Fenster für ACL-Verwaltung.

Up

diff --git a/doc/revpioptionlegacy.html b/doc/revpioptionlegacy.html new file mode 100644 index 0000000..732b51c --- /dev/null +++ b/doc/revpioptionlegacy.html @@ -0,0 +1,164 @@ + + +revpioptionlegacy + + + +

+revpioptionlegacy

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + +
RevPiOption
+

+Functions

+ + +
None
+

+ +

RevPiOption

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiOptionInit RevPiOption-Class.
_changesdonePrüft ob sich die Einstellungen geändert haben.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt Widgets.
_loadappdataLäd aktuelle Einstellungen vom RevPi.
_setappdataSpeichert geänderte Einstellungen auf RevPi.
askxmlonFragt Nuter, ob wirklicht abgeschaltet werden soll.
xmlmod2_tailPasst XML-Optionszugriff an.
xmlmod_tailPasst XML-Optionszugriff an.
+

+Static Methods

+ + +
None
+ +

+RevPiOption (Constructor)

+RevPiOption(master, xmlcli) +

+Init RevPiOption-Class. +

+
Returns:
+
+None +
+
+

+RevPiOption._changesdone

+_changesdone() +

+Prüft ob sich die Einstellungen geändert haben. +

+
Returns:
+
+True, wenn min. eine Einstellung geändert wurde +
+
+

+RevPiOption._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiOption._createwidgets

+_createwidgets() +

+Erstellt Widgets. +

+

+RevPiOption._loadappdata

+_loadappdata(refresh=False) +

+Läd aktuelle Einstellungen vom RevPi. +

+
refresh
+
+Wenn True, werden Einstellungen heruntergeladen. +
+
+

+RevPiOption._setappdata

+_setappdata() +

+Speichert geänderte Einstellungen auf RevPi. +

+
Returns:
+
+None +
+
+

+RevPiOption.askxmlon

+askxmlon() +

+Fragt Nuter, ob wirklicht abgeschaltet werden soll. +

+

+RevPiOption.xmlmod2_tail

+xmlmod2_tail() +

+Passt XML-Optionszugriff an. +

+

+RevPiOption.xmlmod_tail

+xmlmod_tail() +

+Passt XML-Optionszugriff an. +

+
Up
+
+ \ No newline at end of file diff --git a/doc/revpiplclist.html b/doc/revpiplclist.html index d2f8095..35924db 100644 --- a/doc/revpiplclist.html +++ b/doc/revpiplclist.html @@ -17,7 +17,7 @@ Classes - +
RevPiPlcListTK Fenster.

@@ -31,7 +31,9 @@ Functions



RevPiPlcList

- +

+TK Fenster. +

Derived from

tkinter.Frame diff --git a/revpipycontrol.api b/revpipycontrol.api index 4c807c3..40df179 100644 --- a/revpipycontrol.api +++ b/revpipycontrol.api @@ -1,3 +1,30 @@ +aclmanager.AclManager.__get_acltext?6() +aclmanager.AclManager.__set_acltext?6(value) +aclmanager.AclManager._ask_delete?5() +aclmanager.AclManager._changesdone?5() +aclmanager.AclManager._checkclose?5(event=None) +aclmanager.AclManager._clearfields?5() +aclmanager.AclManager._createwidgets?5() +aclmanager.AclManager._loadfields?5() +aclmanager.AclManager._refreshacls?5() +aclmanager.AclManager._save?5() +aclmanager.AclManager._savefields?5() +aclmanager.AclManager._status_editremove?5(tkevt) +aclmanager.AclManager.acl?7 +aclmanager.AclManager.acltext?7 +aclmanager.AclManager.get_acl?4() +aclmanager.AclManager.root?7 +aclmanager.AclManager?1(master, minlevel, maxlevel, acl_str="", readonly=False) +aclmanager.IpAclManager.__get_acl?6() +aclmanager.IpAclManager.__get_regex_acl?6() +aclmanager.IpAclManager.__iter__?6() +aclmanager.IpAclManager.__set_acl?6(value) +aclmanager.IpAclManager.acl?7 +aclmanager.IpAclManager.get_acllevel?4(ipaddress) +aclmanager.IpAclManager.loadacl?4(str_acl) +aclmanager.IpAclManager.regex_acl?7 +aclmanager.IpAclManager?1(minlevel, maxlevel, acl=None) +aclmanager._?8 mytools.addroot?4(filename) mytools.gettrans?4(proglang=None) revpicheckclient.RevPiCheckClient.__chval?6(device, io, event=None) @@ -41,10 +68,20 @@ revpioption.RevPiOption._createwidgets?5() revpioption.RevPiOption._loadappdata?5(refresh=False) revpioption.RevPiOption._setappdata?5() revpioption.RevPiOption.askxmlon?4() -revpioption.RevPiOption.xmlmod2_tail?4() -revpioption.RevPiOption.xmlmod_tail?4() +revpioption.RevPiOption.btn_slaveacl?4() +revpioption.RevPiOption.btn_xmlacl?4() revpioption.RevPiOption?1(master, xmlcli) revpioption._?8 +revpioptionlegacy.RevPiOption._changesdone?5() +revpioptionlegacy.RevPiOption._checkclose?5(event=None) +revpioptionlegacy.RevPiOption._createwidgets?5() +revpioptionlegacy.RevPiOption._loadappdata?5(refresh=False) +revpioptionlegacy.RevPiOption._setappdata?5() +revpioptionlegacy.RevPiOption.askxmlon?4() +revpioptionlegacy.RevPiOption.xmlmod2_tail?4() +revpioptionlegacy.RevPiOption.xmlmod_tail?4() +revpioptionlegacy.RevPiOption?1(master, xmlcli) +revpioptionlegacy._?8 revpiplclist.RevPiPlcList._checkclose?5(event=None) revpiplclist.RevPiPlcList._createwidgets?5() revpiplclist.RevPiPlcList._saveappdata?5() diff --git a/revpipycontrol.bas b/revpipycontrol.bas index 32a817a..7e6201d 100644 --- a/revpipycontrol.bas +++ b/revpipycontrol.bas @@ -1,3 +1,4 @@ +AclManager ttk.Frame RevPiCheckClient tkinter.Frame RevPiInfo tkinter.Frame RevPiLogfile tkinter.Frame diff --git a/revpipycontrol.e4p b/revpipycontrol.e4p index d0187bd..7c939e0 100644 --- a/revpipycontrol.e4p +++ b/revpipycontrol.e4p @@ -1,15 +1,15 @@ - - + + en_US 66103e2eaf8a762f14d1fd51d8b1c9dcaf35a275 Python3 Console - 0.5.0 + 0.6.0 Sven Sager akira@narux.de @@ -23,6 +23,8 @@ revpipycontrol/revpiprogram.py revpipycontrol/mytools.py revpipycontrol/revpiinfo.py + revpipycontrol/revpioptionlegacy.py + revpipycontrol/aclmanager.py diff --git a/revpipycontrol/aclmanager.py b/revpipycontrol/aclmanager.py new file mode 100644 index 0000000..dbffdcd --- /dev/null +++ b/revpipycontrol/aclmanager.py @@ -0,0 +1,418 @@ +# -*- coding: utf-8 -*- +# +# RevPiPyControl +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +u"""Manager für ACL Einträge.""" +import tkinter +import tkinter.messagebox as tkmsg +from mytools import gettrans +from re import fullmatch +from tkinter import ttk + +# Übersetzung laden +_ = gettrans() + + +class IpAclManager(): + + """Verwaltung fuer IP Adressen und deren ACL Level.""" + + def __init__(self, minlevel, maxlevel, acl=None): + """Init IpAclManager class. + + @param minlevel Smallest access level (min. 0) + @param maxlevel Biggest access level (max. 9) + @param acl ACL Liste fuer Berechtigungen als + + """ + if type(minlevel) != int: + raise ValueError("parameter minlevel must be ") + if type(maxlevel) != int: + raise ValueError("parameter maxlevel must be ") + if minlevel < 0: + raise ValueError("minlevel must be 0 or more") + if maxlevel > 9: + raise ValueError("maxlevel maximum is 9") + if minlevel > maxlevel: + raise ValueError("minlevel is smaller than maxlevel") + + self.__dict_acl = {} + self.__dict_regex = {} + self.__dict_knownips = {} + self.__re_ipacl = "(([\\d\\*]{1,3}\\.){3}[\\d\\*]{1,3},[" \ + + str(minlevel) + "-" + str(maxlevel) + "] ?)*" + + # Liste erstellen, wenn übergeben + if acl is not None: + self.__set_acl(acl) + + def __iter__(self): + """Gibt einzelne ACLs als aus.""" + for aclip in sorted(self.__dict_acl): + yield (aclip, self.__dict_acl[aclip]) + + def __get_acl(self): + """Getter fuer den rohen ACL-String. + return ACLs als """ + str_acl = "" + for aclip in sorted(self.__dict_acl): + str_acl += "{},{} ".format(aclip, self.__dict_acl[aclip]) + return str_acl.strip() + + def __get_regex_acl(self): + """Gibt formatierten RegEx-String zurueck. + return RegEx Code als """ + return self.__re_ipacl + + def __set_acl(self, value): + """Uebernimmt neue ACL-Liste fuer die Ausertung der Level. + @param value Neue ACL-Liste als """ + if type(value) != str: + raise ValueError("parameter acl must be ") + + value = value.strip() + if fullmatch(self.__re_ipacl, value) is None: + raise ValueError("acl format ist not okay - 1.2.3.4,0 5.6.7.8,1") + + # Klassenwerte übernehmen + self.__dict_acl = {} + self.__dict_regex = {} + self.__dict_knownips = {} + + # Liste neu füllen mit regex Strings + for ip_level in value.split(): + ip, level = ip_level.split(",", 1) + self.__dict_acl[ip] = int(level) + self.__dict_regex[ip] = \ + ip.replace(".", "\\.").replace("*", "\\d{1,3}") + + def get_acllevel(self, ipaddress): + """Prueft IP gegen ACL List und gibt ACL-Wert aus. + @param ipaddress zum pruefen + @return ACL Wert oder -1 wenn nicht gefunden""" + # Bei bereits aufgelösten IPs direkt ACL auswerten + if ipaddress in self.__dict_knownips: + return self.__dict_knownips[ipaddress] + + for aclip in sorted(self.__dict_acl, reverse=True): + if fullmatch(self.__dict_regex[aclip], ipaddress) is not None: + # IP und Level merken + self.__dict_knownips[ipaddress] = self.__dict_acl[aclip] + + # Level zurückgeben + return self.__dict_acl[aclip] + + return -1 + + def loadacl(self, str_acl): + """Laed ACL String und gibt erfolg zurueck. + @param str_acl ACL als + @return True, wenn erfolgreich uebernommen""" + if fullmatch(self.__re_ipacl, str_acl) is None: + return False + self.__set_acl(str_acl) + return True + + acl = property(__get_acl, __set_acl) + regex_acl = property(__get_regex_acl) + + +class AclManager(ttk.Frame): + + u"""Hauptfenster des ACL-Managers.""" + + def __init__(self, master, minlevel, maxlevel, acl_str="", readonly=False): + u"""Init RevPiOption-Class. + @return None""" + super().__init__(master) + self.master.bind("", self._checkclose) + self.master.protocol("WM_DELETE_WINDOW", self._checkclose) + self.pack(expand=True, fill="both") + + # Daten laden + self.__acl = IpAclManager(minlevel, maxlevel, acl_str) + self.__dict_acltext = {} + self.__oldacl = self.__acl.acl + self.__ro = "disabled" if readonly else "normal" + self.maxlevel = maxlevel + self.minlevel = minlevel + + # Fenster bauen + self._createwidgets() + + def __get_acltext(self): + """Getter fuer Leveltexte. + @return Leveltexte als """ + return self.__dict_acltext.copy() + + def __set_acltext(self, value): + """Setter fuer Leveltexte. + @param value Leveltexte als """ + if type(value) != dict: + raise ValueError("value must be ") + self.__dict_acltext = value.copy() + + # Infotexte aufbauen + self.aclinfo.destroy() + self.aclinfo = ttk.Frame(self) + for acltext in self.__dict_acltext: + lbl = ttk.Label(self.aclinfo) + lbl["text"] = _("Level") + " {}: {}".format( + acltext, self.__dict_acltext[acltext] + ) + lbl.pack(anchor="w") + + self.aclinfo.pack(anchor="w", padx=4, pady=4) + + def _changesdone(self): + u"""Prüft ob sich die Einstellungen geändert haben. + @return True, wenn min. eine Einstellung geändert wurde""" + return not self.__acl.acl == self.__oldacl + + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + ask = True + if self._changesdone(): + ask = tkmsg.askyesno( + _("Question"), + _("Do you really want to quit? \nUnsaved changes will " + "be lost"), + parent=self.master, default="no" + ) + + if ask: + self.master.destroy() + + def _createwidgets(self): + u"""Erstellt Widgets.""" + self.master.wm_title(_("IP access control list")) + self.master.wm_resizable(width=False, height=False) + + cpadw = {"padx": 4, "pady": 2, "sticky": "w"} + + # Gruppe Bestehende ACL ###################################### + gb_acl = ttk.LabelFrame(self) + gb_acl["text"] = _("Existing ACLs") + gb_acl.columnconfigure(0, weight=1) + gb_acl.columnconfigure(1, weight=1) + gb_acl.pack(expand=True, fill="both", padx=4, pady=4) + + row = 0 + frame = ttk.Frame(gb_acl) + frame.columnconfigure(0, weight=1) + + scb_acl = ttk.Scrollbar(frame) + + self.trv_acl = ttk.Treeview(frame) + self.trv_acl["columns"] = ("level") + self.trv_acl["yscrollcommand"] = scb_acl.set + self.trv_acl.heading("level", text=_("Access level")) + self.trv_acl.column("level", width=100) + self.trv_acl.bind("<>", self._status_editremove) + self._refreshacls() + self.trv_acl.grid(row=0, column=0, sticky="we") + + scb_acl["command"] = self.trv_acl.yview + scb_acl.grid(row=0, column=1, sticky="ns") + + frame.grid(row=row, columnspan=2, sticky="we") + + row = 1 + + # Edit / Remove button + self.btn_edit = ttk.Button(gb_acl) + self.btn_edit["command"] = self._loadfields + self.btn_edit["text"] = _("load entry") + self.btn_edit["state"] = "disabled" + self.btn_edit.grid(row=row, column=0, pady=4) + + self.btn_remove = ttk.Button(gb_acl) + self.btn_remove["command"] = self._ask_delete + self.btn_remove["text"] = _("remove entry") + self.btn_remove["state"] = "disabled" + self.btn_remove.grid(row=row, column=1, pady=4) + + # ############################################################ + + # Gruppe Bearbeiten ########################################## + gb_edit = ttk.LabelFrame(self) + gb_edit["text"] = _("Edit acess control list") + gb_edit.pack(expand=True, fill="both", padx=4, pady=4) + + frame = ttk.Frame(gb_edit) + frame.grid() + + row = 0 + lbl = ttk.Label(frame) + lbl["text"] = _("IP address") + ": " + lbl.grid(row=row, column=0, **cpadw) + + # Variablen IP / Level + self.var_ip1 = tkinter.StringVar(frame) + self.var_ip2 = tkinter.StringVar(frame) + self.var_ip3 = tkinter.StringVar(frame) + self.var_ip4 = tkinter.StringVar(frame) + self.var_acl = tkinter.StringVar(frame, self.minlevel) + + ip_block = ttk.Entry(frame, width=4) + ip_block["state"] = self.__ro + ip_block["textvariable"] = self.var_ip1 + ip_block.grid(row=row, column=1) + ip_block = ttk.Entry(frame, width=4) + ip_block["state"] = self.__ro + ip_block["textvariable"] = self.var_ip2 + ip_block.grid(row=row, column=3) + ip_block = ttk.Entry(frame, width=4) + ip_block["state"] = self.__ro + ip_block["textvariable"] = self.var_ip3 + ip_block.grid(row=row, column=5) + ip_block = ttk.Entry(frame, width=4) + ip_block["state"] = self.__ro + ip_block["textvariable"] = self.var_ip4 + ip_block.grid(row=row, column=7) + + # Punkte zwischen IP-Feldern + for i in range(2, 7, 2): + lbl = ttk.Label(frame, text=".") + lbl.grid(row=row, column=i) + + row = 1 + lbl = ttk.Label(frame) + lbl["text"] = _("Access level") + ": " + lbl.grid(row=row, column=0, **cpadw) + + self.sb_level = tkinter.Spinbox(frame, width=4) + self.sb_level["from_"] = self.minlevel + self.sb_level["to"] = self.maxlevel + self.sb_level["state"] = self.__ro + self.sb_level["textvariable"] = self.var_acl + self.sb_level.grid(row=row, column=1, columnspan=8, sticky="w") + + # Buttons neben IP-Eintrag + self.btn_add = ttk.Button(gb_edit) + self.btn_add["command"] = self._savefields + self.btn_add["state"] = self.__ro + self.btn_add["text"] = _("add to list") + self.btn_add.grid(column=0, row=1, sticky="e", padx=4, pady=4) + self.btn_clear = ttk.Button(gb_edit) + self.btn_clear["command"] = self._clearfields + self.btn_clear["state"] = self.__ro + self.btn_clear["text"] = _("clear") + self.btn_clear.grid(column=1, row=1, padx=4, pady=4) + + # ############################################################ + + frame = ttk.Frame(self) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + frame.pack(expand=True, fill="both", pady=4) + + # Buttons + btn_save = ttk.Button(frame) + btn_save["command"] = self._save + btn_save["state"] = self.__ro + btn_save["text"] = _("Save") + btn_save.grid(column=0, row=0) + + btn_close = ttk.Button(frame) + btn_close["command"] = self._checkclose + btn_close["text"] = _("Close") + btn_close.grid(column=1, row=0) + + # Infotexte vorbereiten + self.aclinfo = ttk.Frame(self) + + def _ask_delete(self): + u"""Löscht ein Eintrag der Liste.""" + str_acl = self.trv_acl.focus() + if str_acl != "": + lst_ipacl = str_acl.split() + ask = tkmsg.askyesno( + _("Question"), + _("Do you really want to delete the following item? \n" + "\nIP: {} / Level: {}").format(*lst_ipacl), + parent=self.master, default="no" + ) + if ask: + self.__acl.loadacl( + self.__acl.acl.replace( + "{},{}".format(*lst_ipacl), "" + ).replace(" ", " ") + ) + self._refreshacls() + + def _clearfields(self): + u"""Leert die Eingabefelder.""" + self.var_ip1.set("") + self.var_ip2.set("") + self.var_ip3.set("") + self.var_ip4.set("") + self.var_acl.set(self.minlevel) + + def _loadfields(self): + u"""Übernimmt Listeneintrag in Editfelder.""" + str_acl = self.trv_acl.focus() + if str_acl != "": + lst_ip, acl = str_acl.split() + lst_ip = lst_ip.split(".") + self.var_ip1.set(lst_ip[0]) + self.var_ip2.set(lst_ip[1]) + self.var_ip3.set(lst_ip[2]) + self.var_ip4.set(lst_ip[3]) + self.var_acl.set(acl) + + def _refreshacls(self): + u"""Leert die ACL Liste und füllt sie neu.""" + self.trv_acl.delete(*self.trv_acl.get_children()) + for tup_acl in self.__acl: + self.trv_acl.insert( + "", "end", tup_acl, text=tup_acl[0], values=tup_acl[1] + ) + + def _save(self): + u"""Übernimt die Änderungen.""" + self.__oldacl = self.__acl.acl + + def _savefields(self): + u"""Übernimmt neuen ACL Eintrag.""" + new_acl = "{}.{}.{}.{},{}".format( + self.var_ip1.get(), + self.var_ip2.get(), + self.var_ip3.get(), + self.var_ip4.get(), + self.var_acl.get() + ) + if self.__acl.loadacl(self.__acl.acl + " " + new_acl): + self._refreshacls() + else: + tkmsg.showerror( + _("Error"), + _("Can not load new ACL! Check format."), + parent=self.master + ) + + def _status_editremove(self, tkevt): + u"""Setzt state der Buttons.""" + if self.__ro == "normal": + status = "disabled" if self.trv_acl.focus() == "" else "normal" + self.btn_edit["state"] = status + self.btn_remove["state"] = status + + def get_acl(self): + u"""Gibt die Konfigurierten ACL zurück. + @return ACL als """ + return self.__oldacl + + acl = property(get_acl) + acltext = property(__get_acltext, __set_acltext) + + +if __name__ == "__main__": + root = AclManager(tkinter.Tk(), 0, 9, " 192.168.50.100,2 127.0.0.*,1") + root.acltext = {0: "Keine Rechte", 1: "Hohe Rechte"} + root.mainloop() + print(root.acl) diff --git a/revpipycontrol/revpioption.py b/revpipycontrol/revpioption.py index da41849..6f6aa16 100644 --- a/revpipycontrol/revpioption.py +++ b/revpipycontrol/revpioption.py @@ -7,6 +7,7 @@ # import tkinter import tkinter.messagebox as tkmsg +from aclmanager import AclManager from mytools import gettrans # Übersetzung laden @@ -29,9 +30,10 @@ class RevPiOption(tkinter.Frame): self.master.protocol("WM_DELETE_WINDOW", self._checkclose) self.pack(expand=True, fill="both") + # XML-RPC Server konfigurieren self.xmlcli = xmlcli - self.mrk_var_xmlmod2 = False - self.mrk_var_xmlmod3 = False + self.xmlmodus = self.xmlcli.xmlmodus() + self.mrk_xmlmodask = False self.dorestart = False @@ -43,18 +45,20 @@ class RevPiOption(tkinter.Frame): u"""Prüft ob sich die Einstellungen geändert haben. @return True, wenn min. eine Einstellung geändert wurde""" return ( - self.var_start.get() != self.dc.get("autostart", "1") - or self.var_reload.get() != self.dc.get("autoreload", "1") - or self.var_zexit.get() != self.dc.get("zeroonexit", "0") - or self.var_zerr.get() != self.dc.get("zeroonerror", "0") + self.var_start.get() != self.dc.get("autostart", 1) + or self.var_reload.get() != self.dc.get("autoreload", 1) + or self.var_reload_delay.get() != + str(self.dc.get("autoreloaddelay", 5)) + or self.var_zexit.get() != self.dc.get("zeroonexit", 0) + or self.var_zerr.get() != self.dc.get("zeroonerror", 0) + # TODO: rtlevel (0) or self.var_startpy.get() != self.dc.get("plcprogram", "none.py") or self.var_startargs.get() != self.dc.get("plcarguments", "") - or self.var_pythonver.get() != self.dc.get("pythonversion", "3") - or self.var_slave.get() != self.dc.get("plcslave", "0") - or self.var_xmlon.get() != (self.dc.get("xmlrpc", 0) >= 1) - or self.var_xmlmod2.get() != (self.dc.get("xmlrpc", 0) >= 2) - or self.var_xmlmod3.get() != (self.dc.get("xmlrpc", 0) >= 3) - or self.var_xmlport.get() != self.dc.get("xmlrpcport", "55123") + or self.var_pythonver.get() != self.dc.get("pythonversion", 3) + or self.var_slave.get() != self.dc.get("plcslave", 0) + or self.var_slaveacl.get() != self.dc.get("plcslaveacl", "") + or self.var_xmlon.get() != self.dc.get("xmlrpc", 0) + or self.var_xmlacl.get() != self.dc.get("xmlrpcacl", "") ) def _checkclose(self, event=None): @@ -77,8 +81,9 @@ class RevPiOption(tkinter.Frame): self.master.wm_title(_("RevPi Python PLC Options")) self.master.wm_resizable(width=False, height=False) - xmlstate = "normal" if self.dc["xmlrpc"] >= 3 else "disabled" + xmlstate = "normal" if self.xmlmodus >= 4 else "disabled" + cpade = {"padx": 4, "pady": 2, "sticky": "e"} cpadw = {"padx": 4, "pady": 2, "sticky": "w"} cpadwe = {"padx": 4, "pady": 2, "sticky": "we"} @@ -89,6 +94,7 @@ class RevPiOption(tkinter.Frame): self.var_start = tkinter.BooleanVar(stst) self.var_reload = tkinter.BooleanVar(stst) + self.var_reload_delay = tkinter.StringVar(stst) self.var_zexit = tkinter.BooleanVar(stst) self.var_zerr = tkinter.BooleanVar(stst) @@ -104,24 +110,26 @@ class RevPiOption(tkinter.Frame): ckb_reload["variable"] = self.var_reload ckb_reload.grid(**cpadw) + lbl = tkinter.Label(stst) + lbl["text"] = _("Set process image to NULL if program terminates...") + lbl.grid(**cpadw) + ckb_zexit = tkinter.Checkbutton(stst, justify="left") ckb_zexit["state"] = xmlstate - ckb_zexit["text"] = _( - "Set process image to NULL if program\n" - "terminates successfully") + ckb_zexit["text"] = _("... successfully") ckb_zexit["variable"] = self.var_zexit ckb_zexit.grid(**cpadw) ckb_zerr = tkinter.Checkbutton(stst, justify="left") ckb_zerr["state"] = xmlstate - ckb_zerr["text"] = _( - "Set process image to NULL if program\n" - "terminates with errors") + ckb_zerr["text"] = _("... with errors") ckb_zerr["variable"] = self.var_zerr ckb_zerr.grid(**cpadw) # Gruppe Programm prog = tkinter.LabelFrame(self) + prog.columnconfigure(0, weight=1) + prog.columnconfigure(1, weight=1) prog["text"] = _("PLC program") prog.grid(columnspan=2, pady=2, sticky="we") @@ -129,18 +137,20 @@ class RevPiOption(tkinter.Frame): self.var_startpy = tkinter.StringVar(prog) self.var_startargs = tkinter.StringVar(prog) self.var_slave = tkinter.BooleanVar(prog) + self.var_slaveacl = tkinter.StringVar(prog) self.var_pythonver.set(3) lbl = tkinter.Label(prog) - lbl["text"] = _("Python version") + lbl["text"] = _("Python version") + ":" lbl.grid(columnspan=2, row=0, **cpadw) + rbn = tkinter.Radiobutton(prog) rbn["state"] = xmlstate rbn["text"] = "Python2" rbn["value"] = 2 rbn["variable"] = self.var_pythonver - rbn.grid(column=0, row=1, **cpadw) + rbn.grid(column=0, row=1, **cpade) rbn = tkinter.Radiobutton(prog) rbn["state"] = xmlstate @@ -149,32 +159,42 @@ class RevPiOption(tkinter.Frame): rbn["variable"] = self.var_pythonver rbn.grid(column=1, row=1, **cpadw) + # Row 2 lbl = tkinter.Label(prog) lbl["text"] = _("Python PLC program name") lbl.grid(columnspan=2, **cpadw) + # Row 3 lst = self.xmlcli.get_filelist() if len(lst) == 0: lst.append("none") opt_startpy = tkinter.OptionMenu( - prog, self.var_startpy, *lst) + prog, self.var_startpy, *lst + ) opt_startpy["state"] = xmlstate opt_startpy.grid(columnspan=2, **cpadwe) + # Row 4 lbl = tkinter.Label(prog) lbl["text"] = _("Program arguments") lbl.grid(columnspan=2, **cpadw) + # Row 5 txt = tkinter.Entry(prog) txt["textvariable"] = self.var_startargs txt.grid(columnspan=2, **cpadw) + # Row 6 ckb_slave = tkinter.Checkbutton(prog, justify="left") ckb_slave["state"] = xmlstate ckb_slave["text"] = _("Use RevPi as PLC-Slave") - ckb_slave["state"] = "disabled" ckb_slave["variable"] = self.var_slave - ckb_slave.grid(columnspan=2, **cpadw) + ckb_slave.grid(column=0, **cpadw) + + btn_slaveacl = tkinter.Button(prog, justify="center") + btn_slaveacl["command"] = self.btn_slaveacl + btn_slaveacl["text"] = _("Edit ACL") + btn_slaveacl.grid(column=1, row=6, **cpade) # Gruppe XMLRPC xmlrpc = tkinter.LabelFrame(self) @@ -182,10 +202,7 @@ class RevPiOption(tkinter.Frame): xmlrpc.grid(columnspan=2, pady=2, sticky="we") self.var_xmlon = tkinter.BooleanVar(xmlrpc) - self.var_xmlmod2 = tkinter.BooleanVar(xmlrpc) - self.var_xmlmod3 = tkinter.BooleanVar(xmlrpc) - self.var_xmlport = tkinter.StringVar(xmlrpc) - self.var_xmlport.set("55123") + self.var_xmlacl = tkinter.StringVar(xmlrpc) ckb_xmlon = tkinter.Checkbutton(xmlrpc) ckb_xmlon["command"] = self.askxmlon @@ -194,31 +211,10 @@ class RevPiOption(tkinter.Frame): ckb_xmlon["variable"] = self.var_xmlon ckb_xmlon.grid(**cpadw) - self.ckb_xmlmod2 = tkinter.Checkbutton(xmlrpc, justify="left") - self.ckb_xmlmod2["command"] = self.xmlmod2_tail - self.ckb_xmlmod2["state"] = xmlstate - self.ckb_xmlmod2["text"] = \ - _("Allow download of piCtory configuration and\nPLC programm") - self.ckb_xmlmod2["variable"] = self.var_xmlmod2 - self.ckb_xmlmod2.grid(**cpadw) - - self.ckb_xmlmod3 = tkinter.Checkbutton(xmlrpc, justify="left") - self.ckb_xmlmod3["state"] = xmlstate - self.ckb_xmlmod3["text"] = \ - _("Allow upload of piCtory configuration and\nPLC programm") - self.ckb_xmlmod3["variable"] = self.var_xmlmod3 - self.ckb_xmlmod3.grid(**cpadw) - - lbl = tkinter.Label(xmlrpc) - lbl["text"] = _("XML-RPC server port") - lbl.grid(**cpadw) - - spb_xmlport = tkinter.Spinbox(xmlrpc) - spb_xmlport["to"] = 65535 - spb_xmlport["from"] = 1024 - spb_xmlport["state"] = xmlstate - spb_xmlport["textvariable"] = self.var_xmlport - spb_xmlport.grid(**cpadwe) + btn_slaveacl = tkinter.Button(xmlrpc, justify="center") + btn_slaveacl["command"] = self.btn_xmlacl + btn_slaveacl["text"] = _("Edit ACL") + btn_slaveacl.grid(column=1, row=0, **cpade) # Buttons btn_save = tkinter.Button(self) @@ -238,23 +234,22 @@ class RevPiOption(tkinter.Frame): if refresh: self.dc = self.xmlcli.get_config() - self.var_start.set(self.dc.get("autostart", "1")) - self.var_reload.set(self.dc.get("autoreload", "1")) - self.var_zexit.set(self.dc.get("zeroonexit", "0")) - self.var_zerr.set(self.dc.get("zeroonerror", "0")) + self.var_start.set(self.dc.get("autostart", 1)) + self.var_reload.set(self.dc.get("autoreload", 1)) + self.var_reload_delay.set(self.dc.get("autoreloaddelay", 5)) + self.var_zexit.set(self.dc.get("zeroonexit", 0)) + self.var_zerr.set(self.dc.get("zeroonerror", 0)) + # TODO: rtlevel (0) self.var_startpy.set(self.dc.get("plcprogram", "none.py")) self.var_startargs.set(self.dc.get("plcarguments", "")) - self.var_pythonver.set(self.dc.get("pythonversion", "3")) - self.var_slave.set(self.dc.get("plcslave", "0")) + self.var_pythonver.set(self.dc.get("pythonversion", 3)) - self.var_xmlon.set(self.dc.get("xmlrpc", 0) >= 1) - self.var_xmlmod2.set(self.dc.get("xmlrpc", 0) >= 2) - self.mrk_var_xmlmod2 = self.var_xmlmod2.get() - self.var_xmlmod3.set(self.dc.get("xmlrpc", 0) >= 3) - self.mrk_var_xmlmod3 = self.var_xmlmod3.get() + self.var_slave.set(self.dc.get("plcslave", 0)) + self.var_slaveacl.set(self.dc.get("plcslaveacl", "")) - self.var_xmlport.set(self.dc.get("xmlrpcport", "55123")) + self.var_xmlon.set(self.dc.get("xmlrpc", 0)) + self.var_xmlacl.set(self.dc.get("xmlrpcacl", "")) def _setappdata(self): u"""Speichert geänderte Einstellungen auf RevPi. @@ -276,25 +271,21 @@ class RevPiOption(tkinter.Frame): parent=self.master ) if ask is not None: - self.dc["autostart"] = int(self.var_start.get()) self.dc["autoreload"] = int(self.var_reload.get()) - self.dc["zeroonexit"] = int(self.var_zexit.get()) - self.dc["zeroonerror"] = int(self.var_zerr.get()) - + self.dc["autoreloaddelay"] = int(self.var_reload_delay.get()) + self.dc["autostart"] = int(self.var_start.get()) self.dc["plcprogram"] = self.var_startpy.get() self.dc["plcarguments"] = self.var_startargs.get() self.dc["pythonversion"] = self.var_pythonver.get() + # TODO: rtlevel (0) + self.dc["zeroonerror"] = int(self.var_zerr.get()) + self.dc["zeroonexit"] = int(self.var_zexit.get()) + self.dc["plcslave"] = int(self.var_slave.get()) + self.dc["plcslaveacl"] = self.var_slaveacl.get() - self.dc["xmlrpc"] = 0 - if self.var_xmlon.get(): - self.dc["xmlrpc"] += 1 - if self.var_xmlmod2.get(): - self.dc["xmlrpc"] += 1 - if self.var_xmlmod3.get(): - self.dc["xmlrpc"] += 1 - - self.dc["xmlrpcport"] = self.var_xmlport.get() + self.dc["xmlrpc"] = int(self.var_xmlon.get()) + self.dc["xmlrpcacl"] = self.var_xmlacl.get() if self.xmlcli.set_config(self.dc, ask): tkmsg.showinfo( @@ -325,25 +316,39 @@ class RevPiOption(tkinter.Frame): if not self.mrk_xmlmodask: self.var_xmlon.set(True) - self.xmlmod_tail() + def btn_slaveacl(self): + u"""Öffnet Fenster für ACL-Verwaltung.""" + win = tkinter.Toplevel(self) + win.focus_set() + win.grab_set() + slaveacl = AclManager( + win, 0, 1, + self.var_slaveacl.get(), + readonly=self.xmlmodus < 4 + ) + slaveacl.acltext = { + 0: _("read only"), + 1: _("read and write") + } + self.wait_window(win) + self.var_slaveacl.set(slaveacl.acl) - def xmlmod_tail(self): - u"""Passt XML-Optionszugriff an.""" - if self.var_xmlon.get(): - self.var_xmlmod2.set(self.mrk_var_xmlmod2) - self.ckb_xmlmod2["state"] = "normal" - else: - self.mrk_var_xmlmod2 = self.var_xmlmod2.get() - self.var_xmlmod2.set(False) - self.ckb_xmlmod2["state"] = "disabled" - self.xmlmod2_tail() - - def xmlmod2_tail(self): - u"""Passt XML-Optionszugriff an.""" - if self.var_xmlmod2.get(): - self.var_xmlmod3.set(self.mrk_var_xmlmod3) - self.ckb_xmlmod3["state"] = "normal" - else: - self.mrk_var_xmlmod3 = self.var_xmlmod3.get() - self.var_xmlmod3.set(False) - self.ckb_xmlmod3["state"] = "disabled" + def btn_xmlacl(self): + u"""Öffnet Fenster für ACL-Verwaltung.""" + win = tkinter.Toplevel(self) + win.focus_set() + win.grab_set() + slaveacl = AclManager( + win, 0, 4, + self.var_xmlacl.get(), + readonly=self.xmlmodus < 4 + ) + slaveacl.acltext = { + 0: _("Start/Stop PLC program and read logs"), + 1: _("+ read IOs in watch modus"), + 2: _("+ read properties and download PLC program"), + 3: _("+ upload PLC program"), + 4: _("+ set properties") + } + self.wait_window(win) + self.var_xmlacl.set(slaveacl.acl) diff --git a/revpipycontrol/revpioptionlegacy.py b/revpipycontrol/revpioptionlegacy.py new file mode 100644 index 0000000..7cda9ff --- /dev/null +++ b/revpipycontrol/revpioptionlegacy.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +# +# RevPiPyControl +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +import tkinter +import tkinter.messagebox as tkmsg +from mytools import gettrans + +# Übersetzung laden +_ = gettrans() + + +class RevPiOption(tkinter.Frame): + + def __init__(self, master, xmlcli): + u"""Init RevPiOption-Class. + @return None""" + try: + self.dc = xmlcli.get_config() + except: + self.dc = None + return None + + super().__init__(master) + self.master.bind("", self._checkclose) + self.master.protocol("WM_DELETE_WINDOW", self._checkclose) + self.pack(expand=True, fill="both") + + self.xmlcli = xmlcli + self.mrk_var_xmlmod2 = False + self.mrk_var_xmlmod3 = False + self.mrk_xmlmodask = False + self.dorestart = False + + # Fenster bauen + self._createwidgets() + self._loadappdata() + + def _changesdone(self): + u"""Prüft ob sich die Einstellungen geändert haben. + @return True, wenn min. eine Einstellung geändert wurde""" + return ( + self.var_start.get() != self.dc.get("autostart", "1") + or self.var_reload.get() != self.dc.get("autoreload", "1") + or self.var_zexit.get() != self.dc.get("zeroonexit", "0") + or self.var_zerr.get() != self.dc.get("zeroonerror", "0") + or self.var_startpy.get() != self.dc.get("plcprogram", "none.py") + or self.var_startargs.get() != self.dc.get("plcarguments", "") + or self.var_pythonver.get() != self.dc.get("pythonversion", "3") + or self.var_slave.get() != self.dc.get("plcslave", "0") + or self.var_xmlon.get() != (self.dc.get("xmlrpc", 0) >= 1) + or self.var_xmlmod2.get() != (self.dc.get("xmlrpc", 0) >= 2) + or self.var_xmlmod3.get() != (self.dc.get("xmlrpc", 0) >= 3) + # or self.var_xmlport.get() != self.dc.get("xmlrpcport", "55123") + ) + + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + ask = True + if self._changesdone(): + ask = tkmsg.askyesno( + _("Question"), + _("Do you really want to quit? \nUnsaved changes will " + "be lost"), + parent=self.master, default="no" + ) + + if ask: + self.master.destroy() + + def _createwidgets(self): + u"""Erstellt Widgets.""" + self.master.wm_title(_("RevPi Python PLC Options")) + self.master.wm_resizable(width=False, height=False) + + xmlstate = "normal" if self.dc["xmlrpc"] >= 3 else "disabled" + + cpadw = {"padx": 4, "pady": 2, "sticky": "w"} + cpadwe = {"padx": 4, "pady": 2, "sticky": "we"} + + # Gruppe Start/Stop + stst = tkinter.LabelFrame(self) + stst["text"] = _("Start / Stop behavior") + stst.grid(columnspan=2, pady=2, sticky="we") + + self.var_start = tkinter.BooleanVar(stst) + self.var_reload = tkinter.BooleanVar(stst) + self.var_zexit = tkinter.BooleanVar(stst) + self.var_zerr = tkinter.BooleanVar(stst) + + ckb_start = tkinter.Checkbutton(stst) + ckb_start["text"] = _("Start program automatically") + ckb_start["state"] = xmlstate + ckb_start["variable"] = self.var_start + ckb_start.grid(**cpadw) + + ckb_reload = tkinter.Checkbutton(stst) + ckb_reload["text"] = _("Restart program after exit") + ckb_reload["state"] = xmlstate + ckb_reload["variable"] = self.var_reload + ckb_reload.grid(**cpadw) + + lbl = tkinter.Label(stst) + lbl["text"] = _("Set process image to NULL if program terminates...") + lbl.grid(**cpadw) + + ckb_zexit = tkinter.Checkbutton(stst, justify="left") + ckb_zexit["state"] = xmlstate + ckb_zexit["text"] = _("... successfully") + ckb_zexit["variable"] = self.var_zexit + ckb_zexit.grid(**cpadw) + + ckb_zerr = tkinter.Checkbutton(stst, justify="left") + ckb_zerr["state"] = xmlstate + ckb_zerr["text"] = _("... with errors") + ckb_zerr["variable"] = self.var_zerr + ckb_zerr.grid(**cpadw) + + # Gruppe Programm + prog = tkinter.LabelFrame(self) + prog["text"] = _("PLC program") + prog.grid(columnspan=2, pady=2, sticky="we") + + self.var_pythonver = tkinter.IntVar(prog) + self.var_startpy = tkinter.StringVar(prog) + self.var_startargs = tkinter.StringVar(prog) + self.var_slave = tkinter.BooleanVar(prog) + + self.var_pythonver.set(3) + + lbl = tkinter.Label(prog) + lbl["text"] = _("Python version") + lbl.grid(columnspan=2, row=0, **cpadw) + + rbn = tkinter.Radiobutton(prog) + rbn["state"] = xmlstate + rbn["text"] = "Python2" + rbn["value"] = 2 + rbn["variable"] = self.var_pythonver + rbn.grid(column=0, row=1, **cpadw) + + rbn = tkinter.Radiobutton(prog) + rbn["state"] = xmlstate + rbn["text"] = "Python3" + rbn["value"] = 3 + rbn["variable"] = self.var_pythonver + rbn.grid(column=1, row=1, **cpadw) + + # Row 2 + lbl = tkinter.Label(prog) + lbl["text"] = _("Python PLC program name") + lbl.grid(columnspan=2, **cpadw) + + # Row 3 + lst = self.xmlcli.get_filelist() + if len(lst) == 0: + lst.append("none") + opt_startpy = tkinter.OptionMenu( + prog, self.var_startpy, *lst + ) + opt_startpy["state"] = xmlstate + opt_startpy.grid(columnspan=2, **cpadwe) + + # Row 4 + lbl = tkinter.Label(prog) + lbl["text"] = _("Program arguments") + lbl.grid(columnspan=2, **cpadw) + + # Row 5 + txt = tkinter.Entry(prog) + txt["textvariable"] = self.var_startargs + txt.grid(columnspan=2, **cpadw) + + # Row 6 + ckb_slave = tkinter.Checkbutton(prog, justify="left") + ckb_slave["state"] = xmlstate + ckb_slave["text"] = _("Use RevPi as PLC-Slave") + ckb_slave["variable"] = self.var_slave + ckb_slave.grid(column=0, **cpadw) + + # Gruppe XMLRPC + xmlrpc = tkinter.LabelFrame(self) + xmlrpc["text"] = _("XML-RPC server") + xmlrpc.grid(columnspan=2, pady=2, sticky="we") + + self.var_xmlon = tkinter.BooleanVar(xmlrpc) + self.var_xmlmod2 = tkinter.BooleanVar(xmlrpc) + self.var_xmlmod3 = tkinter.BooleanVar(xmlrpc) +# self.var_xmlport = tkinter.StringVar(xmlrpc) +# self.var_xmlport.set("55123") + + ckb_xmlon = tkinter.Checkbutton(xmlrpc) + ckb_xmlon["command"] = self.askxmlon + ckb_xmlon["state"] = xmlstate + ckb_xmlon["text"] = _("Activate XML-RPC server on RevPi") + ckb_xmlon["variable"] = self.var_xmlon + ckb_xmlon.grid(**cpadw) + + self.ckb_xmlmod2 = tkinter.Checkbutton(xmlrpc, justify="left") + self.ckb_xmlmod2["command"] = self.xmlmod2_tail + self.ckb_xmlmod2["state"] = xmlstate + self.ckb_xmlmod2["text"] = \ + _("Allow download of piCtory configuration and\nPLC programm") + self.ckb_xmlmod2["variable"] = self.var_xmlmod2 + self.ckb_xmlmod2.grid(**cpadw) + + self.ckb_xmlmod3 = tkinter.Checkbutton(xmlrpc, justify="left") + self.ckb_xmlmod3["state"] = xmlstate + self.ckb_xmlmod3["text"] = \ + _("Allow upload of piCtory configuration and\nPLC programm") + self.ckb_xmlmod3["variable"] = self.var_xmlmod3 + self.ckb_xmlmod3.grid(**cpadw) + + lbl = tkinter.Label(xmlrpc) + lbl["text"] = _("XML-RPC server port") + lbl.grid(**cpadw) + +# spb_xmlport = tkinter.Spinbox(xmlrpc) +# spb_xmlport["to"] = 65535 +# spb_xmlport["from"] = 1024 +# spb_xmlport["state"] = xmlstate +# spb_xmlport["textvariable"] = self.var_xmlport +# spb_xmlport.grid(**cpadwe) + + # Buttons + btn_save = tkinter.Button(self) + btn_save["command"] = self._setappdata + btn_save["state"] = xmlstate + btn_save["text"] = _("Save") + btn_save.grid(column=0, row=3) + + btn_close = tkinter.Button(self) + btn_close["command"] = self._checkclose + btn_close["text"] = _("Close") + btn_close.grid(column=1, row=3) + + def _loadappdata(self, refresh=False): + u"""Läd aktuelle Einstellungen vom RevPi. + @param refresh Wenn True, werden Einstellungen heruntergeladen.""" + if refresh: + self.dc = self.xmlcli.get_config() + + self.var_start.set(self.dc.get("autostart", "1")) + self.var_reload.set(self.dc.get("autoreload", "1")) + self.var_zexit.set(self.dc.get("zeroonexit", "0")) + self.var_zerr.set(self.dc.get("zeroonerror", "0")) + + self.var_startpy.set(self.dc.get("plcprogram", "none.py")) + self.var_startargs.set(self.dc.get("plcarguments", "")) + self.var_pythonver.set(self.dc.get("pythonversion", "3")) + self.var_slave.set(self.dc.get("plcslave", "0")) + + self.var_xmlon.set(self.dc.get("xmlrpc", 0) >= 1) + self.var_xmlmod2.set(self.dc.get("xmlrpc", 0) >= 2) + self.mrk_var_xmlmod2 = self.var_xmlmod2.get() + self.var_xmlmod3.set(self.dc.get("xmlrpc", 0) >= 3) + self.mrk_var_xmlmod3 = self.var_xmlmod3.get() + +# self.var_xmlport.set(self.dc.get("xmlrpcport", "55123")) + + def _setappdata(self): + u"""Speichert geänderte Einstellungen auf RevPi. + @return None""" + + if not self._changesdone(): + tkmsg.showinfo( + _("Information"), + _("You have not made any changes to save."), + ) + self._checkclose() + return None + + ask = tkmsg.askyesnocancel( + _("Question"), + _("The settings are now saved on the Revolution Pi. \n\n" + "Should the new settings take effect immediately? \nThis " + "means a restart of the service and the PLC program!"), + parent=self.master + ) + if ask is not None: + self.dc["autostart"] = int(self.var_start.get()) + self.dc["autoreload"] = int(self.var_reload.get()) + self.dc["zeroonexit"] = int(self.var_zexit.get()) + self.dc["zeroonerror"] = int(self.var_zerr.get()) + + self.dc["plcprogram"] = self.var_startpy.get() + self.dc["plcarguments"] = self.var_startargs.get() + self.dc["pythonversion"] = self.var_pythonver.get() + self.dc["plcslave"] = int(self.var_slave.get()) + + self.dc["xmlrpc"] = 0 + if self.var_xmlon.get(): + self.dc["xmlrpc"] += 1 + if self.var_xmlmod2.get(): + self.dc["xmlrpc"] += 1 + if self.var_xmlmod3.get(): + self.dc["xmlrpc"] += 1 + +# self.dc["xmlrpcport"] = self.var_xmlport.get() + + if self.xmlcli.set_config(self.dc, ask): + tkmsg.showinfo( + _("Information"), + _("Settings saved"), + parent=self.master + ) + self.dorestart = ask + self._checkclose() + else: + tkmsg.showerror( + _("Error"), + _("The settings could not be saved. This can happen if " + "values are wrong!"), + parent=self.master + ) + + def askxmlon(self): + u"""Fragt Nuter, ob wirklicht abgeschaltet werden soll.""" + if not (self.var_xmlon.get() or self.mrk_xmlmodask): + self.mrk_xmlmodask = tkmsg.askyesno( + _("Question"), + _("Are you sure you want to deactivate the XML-RPC server? " + "You will NOT be able to access the Revolution Pi with " + "this program."), + parent=self.master + ) + if not self.mrk_xmlmodask: + self.var_xmlon.set(True) + + self.xmlmod_tail() + + def xmlmod_tail(self): + u"""Passt XML-Optionszugriff an.""" + if self.var_xmlon.get(): + self.var_xmlmod2.set(self.mrk_var_xmlmod2) + self.ckb_xmlmod2["state"] = "normal" + else: + self.mrk_var_xmlmod2 = self.var_xmlmod2.get() + self.var_xmlmod2.set(False) + self.ckb_xmlmod2["state"] = "disabled" + self.xmlmod2_tail() + + def xmlmod2_tail(self): + u"""Passt XML-Optionszugriff an.""" + if self.var_xmlmod2.get(): + self.var_xmlmod3.set(self.mrk_var_xmlmod3) + self.ckb_xmlmod3["state"] = "normal" + else: + self.mrk_var_xmlmod3 = self.var_xmlmod3.get() + self.var_xmlmod3.set(False) + self.ckb_xmlmod3["state"] = "disabled" diff --git a/revpipycontrol/revpipycontrol.py b/revpipycontrol/revpipycontrol.py index 24b3bba..1be69d1 100755 --- a/revpipycontrol/revpipycontrol.py +++ b/revpipycontrol/revpipycontrol.py @@ -7,6 +7,7 @@ # Webpage: https://revpimodio.org/revpipyplc/ # (c) Sven Sager, License: LGPLv3 # +u"""Hauptprogramm.""" import revpicheckclient import revpiinfo import revpilogfile @@ -23,7 +24,7 @@ from xmlrpc.client import ServerProxy # Übersetzung laden _ = gettrans() -pycontrolversion = "0.5.0" +pycontrolversion = "0.6.0" class RevPiPyControl(tkinter.Frame): @@ -238,8 +239,8 @@ class RevPiPyControl(tkinter.Frame): _("Warning"), _("The watch mode ist not supported in version {} " "of RevPiPyLoad on your RevPi! You need at least version " - "0.4.0. Or the python3-revpimodio module is not installt" - "on your RevPi at least version 0.15.0." + "0.5.3! Maybe the python3-revpimodio2 module is not " + "installed on your RevPi at least version 2.0.0." "").format(self.cli.version()), parent=self.master ) @@ -256,7 +257,7 @@ class RevPiPyControl(tkinter.Frame): tkmsg.showwarning( _("Error"), _("Can not load piCtory configuration. \n" - "Have you created a hardware configuration? " + "Did you create a hardware configuration? " "Please check this in piCtory!"), parent=self.master ) diff --git a/setup.py b/setup.py index f4ce7e7..fbf68c0 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ globsetup = { "author_email": "akira@narux.de", "url": "https://revpimodio.org/revpipyplc/", "license": "LGPLv3", - "version": "0.5.0", + "version": "0.6.0", "name": "revpipycontrol", diff --git a/stdeb.cfg b/stdeb.cfg index 66b7628..ea28f1d 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -3,3 +3,4 @@ Debian-Version=1 Depends3=python3-tk Section=universe/x11 Suite=stable +X-Python3-Version: >=3.4