Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need a simple SAAS APP example with Remi #499

Closed
Ali-RAFIEE opened this issue Oct 4, 2022 · 18 comments
Closed

Need a simple SAAS APP example with Remi #499

Ali-RAFIEE opened this issue Oct 4, 2022 · 18 comments

Comments

@Ali-RAFIEE
Copy link

Hi everyone,
I just discovered Remi for developing python applications for web use.
I need a simple example for the GUI of SAAS product using Remi.

in fact I would like to launch a python code from a remote pc (outside the pc on which the code is located), is this possible with Remi?

Thanks a lot
Ali

@dddomodossola
Copy link
Collaborator

Hello,

Remi allows to create graphical interfaces for python applications. Such interfaces are accessible from everywhere in your network. I suggest to only expose such applications in private networks or VPN.

Kind Regards.

@Ali-RAFIEE
Copy link
Author

Thanks Davide,
I have some experiace with TKinter for standalone app, but now I need a tool like Remi to convert my codes to network user (in VPN format), so I think Remi can be appropaite for this purpose.

I need just a small script to found out how I can create an shared interface under SAAS form.
Thanks again

@dddomodossola
Copy link
Collaborator

I would be pleaded to help you, but you should give me more details about the requirements. Remi allows to create ui. SaaS is a sw architecture to make an application running as a service, maybe giving it a REST interface. Here we have two different things.. what you want to do exactly?

@Ali-RAFIEE
Copy link
Author

I would like to create an interface for a code, that a user on our internal network can run it from his pc (remote access, without having access to the code, just by having access to the web interface), then the user can give and change some parameters and may be able to view results (calculation results) and some generated graphes.

@dddomodossola
Copy link
Collaborator

There are different examples about remi in this folder
https://github.com/rawpython/remi/tree/master/examples

Additionally there is a graphical editor that can help you design your interface.

@Ali-RAFIEE
Copy link
Author

already I tested some of them, but a problem that I have, is how can I run one simple exmple in this series of test codes from another pc.
I can not configure IP adresse inside of code, to have acces from another pc.

So, real issue it s how can I configure acces with IP adresse ou other way to arrive runing graphic interface via remote PC.

Thanks a lot

@dddomodossola
Copy link
Collaborator

At the end of the script there is the "start" function call, there an ip address is passed as parameter. Set that ip address as 0.0.0.0 and the ui will be reachable from every device in the network

@Ali-RAFIEE
Copy link
Author

I try one example on same wifi connection

I have GUI on my runing PC with this adresse http://127.0.0.1:8081/

but I can not see the same page from another PC.

it is not work for me!!

@dddomodossola
Copy link
Collaborator

On the other machine you should access a different url. The ip address in the url must be the ipaddress of the machine running the application.
http://correct_ip:8081/

@dddomodossola
Copy link
Collaborator

dddomodossola commented Oct 4, 2022

@Ali-RAFIEE did you get it working?

@Ali-RAFIEE
Copy link
Author

Thank you very much Davide
That works.

but is it possible to convert the access into a https secure access?
and also how can i get my code to run directly from a remote PC?

Thanks.

@dddomodossola
Copy link
Collaborator

I'm pleased to help.
Of course it is possible to use https, but you need to create certificates.
You can run your program on every kind of machine, and you can access your interface from everywhere. What you mean for executing the code directly from the remote PC?

@Ali-RAFIEE
Copy link
Author

currently I run my code on my PC and I manage to have the Gui interface on another PC.
but in fact I would like to run the code directly from the remote pc without having to run it first on the PC hosting the code.

@Ali-RAFIEE
Copy link
Author

Do I have to keep the main interface open all the time on the server PC to run a code?

Indeed I want to have the first page which checks the username and password of each user, then if a user wants to do calculations he can perform and at the same time the software home page can accept multiple users simultaneously. Is it possible with Remi? Any simple example?

Thanks a lot

@dddomodossola
Copy link
Collaborator

You have to run the script on the server. You can open and close the interface when you want, the server will continue to listen for new connections. Of course different users can connect to the interface at the same time and perform login separately

@Ali-RAFIEE
Copy link
Author

Ali-RAFIEE commented Oct 6, 2022

And last question:
should I create two separate pages, one for logging (verifying username and password) and another to give access to the user to run the main code?

and how can I manage to get multiple users working simultaneously with the same code but with different inputs and therefore different results to illustrate? should I generate a different port number for each new user?

if there is a simple example it is welcome, starting a new challenge is always a harder part of a project.

Thanks again

@Ali-RAFIEE Ali-RAFIEE changed the title Need a simple SAAS APP with Remi Need a simple SAAS APP example with Remi Oct 7, 2022
@dddomodossola
Copy link
Collaborator

Hello @Ali-RAFIEE ,

Here is an example for you. It is an advanced example, I'm sure you can simplify it.

import remi.gui as gui
from remi.gui import *
from remi import start, App
import random
import threading

class CookieInterface(gui.Tag, gui.EventSource):
    def __init__(self, remi_app_instance, **kwargs):
        super(CookieInterface, self).__init__(**kwargs)
        gui.EventSource.__init__(self)
        self.app_instance = remi_app_instance
        self.EVENT_ONCOOKIES = "on_cookies"
        self.cookies = {}
        
    def request_cookies(self):
        self.app_instance.execute_javascript("""
            var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
            var result = {};
            for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { 
                var key = decodeURIComponent(aKeys[nIdx]);
                result[key] = decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(key).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null; 
            }
            sendCallbackParam('%s','%s', result);
            """%(self.identifier, self.EVENT_ONCOOKIES))

    @gui.decorate_event
    def on_cookies(self, **value):
        self.cookies = value
        return (value,)
    
    def remove_cookie(self, key, path='/', domain=''):
        if not key in self.cookies.keys():
            return
        self.app_instance.execute_javascript( """
            var sKey = "%(sKey)s";
            var sPath = "%(sPath)s";
            var sDomain = "%(sDomain)s";
            document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
            """%{'sKey': key, 'sPath': path, 'sDomain': domain} )

    def set_cookie(self, key, value, expiration='Infinity', path='/', domain='', secure=False):
        """
        expiration (int): seconds after with the cookie automatically gets deleted
        """

        secure = 'true' if secure else 'false'
        self.app_instance.execute_javascript("""
            var sKey = "%(sKey)s";
            var sValue = "%(sValue)s";
            var vEnd = eval("%(vEnd)s");
            var sPath = "%(sPath)s"; 
            var sDomain = "%(sDomain)s"; 
            var bSecure = %(bSecure)s;
            if( (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) == false ){
                var sExpires = "";
                if (vEnd) {
                    switch (vEnd.constructor) {
                        case Number:
                            sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
                        break;
                        case String:
                            sExpires = "; expires=" + vEnd;
                        break;
                        case Date:
                            sExpires = "; expires=" + vEnd.toUTCString();
                        break;
                    }
                }
                document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
            }
            """%{'sKey': key, 'sValue': value, 'vEnd': expiration, 'sPath': path, 'sDomain': domain, 'bSecure': secure})


class User(object):
    USER_LEVEL_NOT_AUTHENTICATED = 0
    USER_LEVEL_USER = 1
    USER_LEVEL_ADMIN = 2

    def __init__(self, username, pwd, user_level=0):
        self.username = username
        self.pwd = pwd
        self.user_level = user_level


class LoginManager(gui.Tag, gui.EventSource):
    """
    Login manager class allows to simply manage user access safety by session cookies
    It requires a cookieInterface instance to query and set user session id
    When the user login to the system you have to call
        login_manager.renew_session() #in order to force new session uid setup

    The session have to be refreshed each user action (like button click or DB access)
    in order to avoid expiration. BUT before renew, check if expired in order to ask user login

        if not login_manager.expired:
            login_manager.renew_session()
            #RENEW OK
        else:
            #UNABLE TO RENEW
            #HAVE TO ASK FOR LOGIN

    In order to know session expiration, you should register to on_session_expired event
        on_session_expired.do(mylistener.on_user_logout)
    When this event happens, ask for user login
    """
    def __init__(self, user_dictionary, cookieInterface, session_timeout_seconds = 60, **kwargs):
        #user_dictionary is a  dictionary (the key is the username, 
        # the value is a User instance) have to be a list of users, can be loaded or other data sources
        super(LoginManager, self).__init__(**kwargs)
        gui.EventSource.__init__(self)
        self.user_dictionary = user_dictionary 
        self.user_logged_instance = None
        self.session_uid = str(random.randint(1,999999999))
        self.cookieInterface = cookieInterface
        self.session_timeout_seconds = session_timeout_seconds
        self.timer_request_cookies() #starts the cookie refresh
        self.timeout_timer = None #checks the internal timeout
    
    def timer_request_cookies(self):
        self.cookieInterface.request_cookies()
        threading.Timer(self.session_timeout_seconds/10.0, self.timer_request_cookies).start()

    @gui.decorate_event
    def on_session_expired(self):
        self.user_logged_instance = None
        return ()

    def renew_session(self):
        """Have to be called on user actions to check and renew session
        """
        if ((not 'user_uid' in self.cookieInterface.cookies) or self.cookieInterface.cookies['user_uid']!=self.session_uid) and (not (self.user_logged_instance==None)):
            self.on_session_expired()

        if self.user_logged_instance == None:
            self.session_uid = str(random.randint(1,999999999))
        
        self.cookieInterface.set_cookie('user_uid', self.session_uid, str(self.session_timeout_seconds))

        #here we renew the internal timeout timer
        if self.timeout_timer:
            self.timeout_timer.cancel()
        self.timeout_timer = threading.Timer(self.session_timeout_seconds, self.on_session_expired)
        self.timeout_timer.start()

    def login(self, username, pwd):
        #returns None if not authenticated or a User instance, with the proper user_level
        if username in self.user_dictionary.keys():
            if pwd == self.user_dictionary[username].pwd:
                print("password ok")
                self.user_logged_instance = self.user_dictionary[username]
                self.renew_session()
                return self.user_dictionary[username] #self.user_logged_instance
        return None

    def add_user(self, user):
        #add a new user that can login, you can save it to a database
        self.user_dictionary[user.username] = user


class ContainerProxy():
    #given a container, this class allows to append widgets to it, keeping memory about widget user level.
    # when user level changes, this class hides or shows children widgets properly
    def __init__(self, container):
        self.container = container
        self.widgets_dictionary = {}

    def append(self, user_level, widget, key=None):
        key = self.container.append(widget, key)
        #here we store for each widget, the minimum user level for which it have to be visible 
        self.widgets_dictionary[key] = {'widget':widget, 'user_level':user_level}

    def set_logged_user_level(self, user_level = User.USER_LEVEL_NOT_AUTHENTICATED):
        self.user_level = user_level
        for widget_id in self.widgets_dictionary.keys():
            if self.widgets_dictionary[widget_id]['user_level'] > self.user_level:
                #here we replace the widget to hide in order to mantain a placeholder
                self.container.append(gui.Widget(style={'display':'none', 'width':'0px', 'height':'0px'}), widget_id)
            else:
                #here we put again the widget on the interface, the placeholder gets replaced because the same kay is used to append
                self.container.append(self.widgets_dictionary[widget_id]['widget'], widget_id)


class MyApp(App):
    def __init__(self, *args, **kwargs):
        super(MyApp, self).__init__(*args, static_file_path='./res/')

    def idle(self):
        #idle function called every update cycle
        pass
    
    def main(self):
        main_container = Container(margin="0px auto")
        self.container_proxy = ContainerProxy(main_container)
        main_container.attributes.update({"editor_baseclass":"Container","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"()","class":"Container","editor_varname":"main_container"})
        main_container.style.update({"width":"580.0px","position":"relative","overflow":"auto","height":"500.0px"})
        user_area_container = Container()
        user_area_container.attributes.update({"editor_baseclass":"Container","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"()","class":"Container","editor_varname":"user_area_container"})
        user_area_container.style.update({"width":"260.0px","border-style":"dotted","border-width":"1px","position":"absolute","top":"40.0px","left":"20.0px","margin":"0px","overflow":"auto","height":"440.0px"})
        lbl_user_panel = Label('User panel')
        lbl_user_panel.attributes.update({"editor_baseclass":"Label","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"('User panel')","class":"Label","editor_varname":"lbl_user_panel"})
        lbl_user_panel.style.update({"width":"100px","position":"absolute","top":"20.0px","left":"20.0px","margin":"0px","overflow":"auto","height":"30px"})
        user_area_container.append(lbl_user_panel,'lbl_user_panel')
        #main_container.append(user_area_container,'user_area_container')
        self.container_proxy.append(User.USER_LEVEL_USER, user_area_container,'user_area_container')
        user_auth_container = HBox()
        user_auth_container.attributes.update({"editor_baseclass":"HBox","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"()","class":"HBox","editor_varname":"user_auth_container"})
        user_auth_container.style.update({"right":"1px","align-items":"center","visibility":"visible","height":"35px","overflow":"auto","top":"1px","flex-direction":"row","width":"300px","justify-content":"space-around","position":"absolute","margin":"0px","display":"flex"})
        bt_user_login = Button('login')
        bt_user_login.attributes.update({"editor_baseclass":"Button","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"('login')","class":"Button","editor_varname":"bt_user_login"})
        bt_user_login.style.update({"width":"100px","position":"static","top":"20px","order":"2","margin":"0px","overflow":"auto","height":"30px"})
        bt_user_login.onclick.do(self.on_prompt_login)
        self.bt_user_login = bt_user_login
        user_auth_container.append(bt_user_login,'bt_user_login')
        user_name = Label('not logged in')
        user_name.attributes.update({"editor_baseclass":"Label","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"('not logged in')","class":"Label","editor_varname":"user_name"})
        user_name.style.update({"width":"100px","font-weight":"bold","position":"static","top":"20px","order":"1","margin":"0px","overflow":"auto","height":"30px"})
        self.user_name = user_name
        user_auth_container.append(user_name,'user_name')
        bt_user_logout = Button('logout')
        bt_user_logout.attributes.update({"editor_baseclass":"Button","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"('logout')","class":"Button","editor_varname":"bt_user_logout"})
        bt_user_logout.style.update({"visibility":"visible","height":"30px","overflow":"auto","top":"20px","order":"3","width":"100px","position":"static","margin":"0px","display":"none","background-color":"#fa3200"})
        bt_user_logout.onclick.do(self.on_logout)
        self.bt_user_logout = bt_user_logout
        user_auth_container.append(bt_user_logout,'bt_user_logout')
        main_container.append(user_auth_container,'user_auth_container')
        admin_area_container = Container()
        admin_area_container.attributes.update({"editor_baseclass":"Container","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"()","class":"Container","editor_varname":"admin_area_container"})
        admin_area_container.style.update({"border-color":"#030303","height":"440.0px","border-style":"dotted","overflow":"auto","top":"40.0px","border-width":"1px","width":"260.0px","position":"absolute","margin":"0px","left":"300.0px"})
        lbl_admin_panel = Label('Administration panel')
        lbl_admin_panel.attributes.update({"editor_baseclass":"Label","editor_tag_type":"widget","editor_newclass":"False","editor_constructor":"('Administration panel')","class":"Label","editor_varname":"lbl_admin_panel"})
        lbl_admin_panel.style.update({"width":"158px","position":"absolute","top":"20.0px","left":"20.0px","margin":"0px","overflow":"auto","height":"27px"})
        admin_area_container.append(lbl_admin_panel,'lbl_admin_panel')
        #main_container.append(admin_area_container,'admin_area_container')
        self.container_proxy.append(User.USER_LEVEL_ADMIN, admin_area_container,'admin_area_container')

        example_users = {}
        example_users['admin'] = User('admin', 'pwdadmin', User.USER_LEVEL_ADMIN)
        example_users['user'] = User('user', 'pwduser', User.USER_LEVEL_USER)
        self.login_manager = LoginManager(example_users, CookieInterface(self), 60*60) #autologout 1 hour (60 seconds x 60 minutes)
        self.login_manager.on_session_expired.do(self.on_logout)

        self.main_container = main_container
        self.on_logout(None)
        return self.main_container

    def on_prompt_login(self, emitter):
        self.login_dialog = gui.GenericDialog("Login", "Type here login data", width=300)
        username_input = gui.TextInput(width=250)
        password_input = gui.TextInput(width=250)
        self.login_dialog.add_field_with_label('username', 'Username', username_input)
        self.login_dialog.add_field_with_label('password', 'Password', password_input)
        self.login_dialog.confirm_dialog.do(self.on_login_confirm)
        self.login_dialog.show(self)

    def on_login_confirm(self, emitter):
        username = self.login_dialog.get_field('username').get_value()
        password = self.login_dialog.get_field('password').get_value()
        user = self.login_manager.login(username, password)
        if user != None:
            print("logged in")
            self.user_name.set_text("hello " + user.username)
            self.bt_user_login.style['display'] = 'none'
            self.bt_user_logout.style['display'] = 'inline'
            self.container_proxy.set_logged_user_level(user.user_level)

    def on_renew(self, emitter):
        #THIS METHOD HAVE TO BE CALLED EACH TIME A USER DOES SOMETHING ON THE INTERFACE
        # IT IS TO SAY EACH "INTERACTION" to keep the access alive, otherwise the user will be disconnected after a timeout
        #if the user is still logged in
        if not self.login_manager.user_logged_instance is None:
            self.login_manager.renew_session()
        else:
            self.on_logout(None)

    def on_logout(self, emitter):
        self.bt_user_login.style['display'] = 'inline'
        self.bt_user_logout.style['display'] = 'none'
        self.user_name.set_text("not logged in")
        self.container_proxy.set_logged_user_level()


if __name__ == "__main__":
    # starts the webserver
    start(MyApp, address='0.0.0.0', port=0, multiple_instance=False, debug=False)

@Ali-RAFIEE
Copy link
Author

Hi Davide
Thanks a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants