فهرست منبع

add configuration and deployment

Falko Schindler 1 سال پیش
والد
کامیت
608c3ba4a3

+ 4 - 0
website/documentation/content/overview.py

@@ -5,6 +5,7 @@ from nicegui import ui
 from ..content.sections.action_events import ActionEventsDocumentation
 from ..content.sections.audiovisual_elements import AudiovisualElementsDocumentation
 from ..content.sections.binding_properties import BindingPropertiesDocumentation
+from ..content.sections.configuration_deployment import ConfigurationDeploymentDocumentation
 from ..content.sections.controls import ControlsDocumentation
 from ..content.sections.data_elements import DataElementsDocumentation
 from ..content.sections.page_layout import PageLayoutDocumentation
@@ -81,6 +82,9 @@ class Overview(Documentation):
             (PagesRoutingDocumentation(), '''
                 A NiceGUI app can consist of multiple pages and other FastAPI endpoints.
             '''),
+            (ConfigurationDeploymentDocumentation(), '''
+                Whether you want to run your app locally or on a server, native or in a browser, we got you covered.
+            '''),
         ]
 
         @self.ui

+ 259 - 0
website/documentation/content/sections/configuration_deployment.py

@@ -0,0 +1,259 @@
+from nicegui import ui
+
+from ...demo import bash_window, python_window
+from ...model import SectionDocumentation
+from ...more.run_documentation import RunDocumentation
+
+
+class ConfigurationDeploymentDocumentation(SectionDocumentation, title='Configuration & Deployment', name='configuration_deployment'):
+
+    def content(self) -> None:
+        @self.demo('URLs', '''
+            You can access the list of all URLs on which the NiceGUI app is available via `app.urls`.
+            The URLs are not available in `app.on_startup` because the server is not yet running.
+            Instead, you can access them in a page function or register a callback with `app.urls.on_change`.
+        ''')
+        def urls_demo():
+            from nicegui import app
+
+            # @ui.page('/')
+            # def index():
+            #     for url in app.urls:
+            #         ui.link(url, target=url)
+            # END OF DEMO
+            ui.link('https://nicegui.io', target='https://nicegui.io')
+
+        self.intro(RunDocumentation())
+
+        @self.demo('Native Mode', '''
+            You can enable native mode for NiceGUI by specifying `native=True` in the `ui.run` function.
+            To customize the initial window size and display mode, use the `window_size` and `fullscreen` parameters respectively.
+            Additionally, you can provide extra keyword arguments via `app.native.window_args` and `app.native.start_args`.
+            Pick any parameter as it is defined by the internally used [pywebview module](https://pywebview.flowrl.com/guide/api.html)
+            for the `webview.create_window` and `webview.start` functions.
+            Note that these keyword arguments will take precedence over the parameters defined in `ui.run`.
+
+            In native mode the `app.native.main_window` object allows you to access the underlying window.
+            It is an async version of [`Window` from pywebview](https://pywebview.flowrl.com/guide/api.html#window-object).
+        ''')  # TODO: tab=lambda: ui.label('NiceGUI')
+        def native_mode_demo():
+            from nicegui import app
+
+            app.native.window_args['resizable'] = False
+            app.native.start_args['debug'] = True
+
+            ui.label('app running in native mode')
+            # ui.button('enlarge', on_click=lambda: app.native.main_window.resize(1000, 700))
+            #
+            # ui.run(native=True, window_size=(400, 300), fullscreen=False)
+            # END OF DEMO
+            ui.button('enlarge', on_click=lambda: ui.notify('window will be set to 1000x700 in native mode'))
+
+        # Show a helpful workaround until issue is fixed upstream.
+        # For more info see: https://github.com/r0x0r/pywebview/issues/1078
+        ui.markdown('''
+            If webview has trouble finding required libraries, you may get an error relating to "WebView2Loader.dll".
+            To work around this issue, try moving the DLL file up a directory, e.g.:
+            
+            * from `.venv/Lib/site-packages/webview/lib/x64/WebView2Loader.dll`
+            * to `.venv/Lib/site-packages/webview/lib/WebView2Loader.dll`
+        ''')
+
+        @self.demo('Environment Variables', '''
+            You can set the following environment variables to configure NiceGUI:
+
+            - `MATPLOTLIB` (default: true) can be set to `false` to avoid the potentially costly import of Matplotlib.
+                This will make `ui.pyplot` and `ui.line_plot` unavailable.
+            - `NICEGUI_STORAGE_PATH` (default: local ".nicegui") can be set to change the location of the storage files.
+            - `MARKDOWN_CONTENT_CACHE_SIZE` (default: 1000): The maximum number of Markdown content snippets that are cached in memory.
+        ''')
+        def env_var_demo():
+            from nicegui.elements import markdown
+
+            ui.label(f'Markdown content cache size is {markdown.prepare_content.cache_info().maxsize}')
+
+        self.text('Server Hosting', '''
+            To deploy your NiceGUI app on a server, you will need to execute your `main.py` (or whichever file contains your `ui.run(...)`) on your cloud infrastructure.
+            You can, for example, just install the [NiceGUI python package via pip](https://pypi.org/project/nicegui/) and use systemd or similar service to start the main script.
+            In most cases, you will set the port to 80 (or 443 if you want to use HTTPS) with the `ui.run` command to make it easily accessible from the outside.
+
+            A convenient alternative is the use of our [pre-built multi-arch Docker image](https://hub.docker.com/r/zauberzeug/nicegui) which contains all necessary dependencies.
+            With this command you can launch the script `main.py` in the current directory on the public port 80:
+        ''')
+
+        @self.ui
+        def docker_run():
+            with bash_window(classes='max-w-lg w-full h-44'):
+                ui.markdown('''
+                    ```bash
+                    docker run -it --restart always \\
+                    -p 80:8080 \\
+                    -e PUID=$(id -u) \\
+                    -e PGID=$(id -g) \\
+                    -v $(pwd)/:/app/ \\
+                    zauberzeug/nicegui:latest
+                    ```
+                ''')
+
+        self.text('', '''
+            The demo assumes `main.py` uses the port 8080 in the `ui.run` command (which is the default).
+            The `-d` tells docker to run in background and `--restart always` makes sure the container is restarted if the app crashes or the server reboots.
+            Of course this can also be written in a Docker compose file:
+        ''')
+
+        @self.ui
+        def docker_compose():
+            with python_window('docker-compose.yml', classes='max-w-lg w-full h-60'):
+                ui.markdown('''
+                    ```yaml
+                    app:
+                        image: zauberzeug/nicegui:latest
+                        restart: always
+                        ports:
+                            - 80:8080
+                        environment:
+                            - PUID=1000 # change this to your user id
+                            - PGID=1000 # change this to your group id
+                        volumes:
+                            - ./:/app/
+                    ```
+                ''')
+
+        self.text('', '''
+            There are other handy features in the Docker image like non-root user execution and signal pass-through.
+            For more details we recommend to have a look at our [Docker example](https://github.com/zauberzeug/nicegui/tree/main/examples/docker_image).
+
+            You can provide SSL certificates directly using [FastAPI](https://fastapi.tiangolo.com/deployment/https/).
+            In production we also like using reverse proxies like [Traefik](https://doc.traefik.io/traefik/) or [NGINX](https://www.nginx.com/) to handle these details for us.
+            See our development [docker-compose.yml](https://github.com/zauberzeug/nicegui/blob/main/docker-compose.yml) as an example.
+
+            You may also have a look at [our demo for using a custom FastAPI app](https://github.com/zauberzeug/nicegui/tree/main/examples/fastapi).
+            This will allow you to do very flexible deployments as described in the [FastAPI documentation](https://fastapi.tiangolo.com/deployment/).
+            Note that there are additional steps required to allow multiple workers.
+        ''')
+
+        self.text('Package for Installation', '''
+            NiceGUI apps can also be bundled into an executable with [PyInstaller](https://www.pyinstaller.org/).
+            This allows you to distribute your app as a single file that can be executed on any computer.
+
+            Just take care your `ui.run` command does not use the `reload` argument.
+            Running the `build.py` below will create an executable `myapp` in the `dist` folder:
+        ''')
+
+        @self.ui
+        def pyinstaller():
+            with ui.row().classes('w-full items-stretch'):
+                with python_window(classes='max-w-lg w-full'):
+                    ui.markdown('''
+                        ```python
+                        from nicegui import native, ui
+
+                        ui.label('Hello from PyInstaller')
+
+                        ui.run(reload=False, port=native.find_open_port())
+                        ```
+                    ''')
+                with python_window('build.py', classes='max-w-lg w-full'):
+                    ui.markdown('''
+                        ```python
+                        import os
+                        import subprocess
+                        from pathlib import Path
+                        import nicegui
+
+                        cmd = [
+                            'python',
+                            '-m', 'PyInstaller',
+                            'main.py', # your main file with ui.run()
+                            '--name', 'myapp', # name of your app
+                            '--onefile',
+                            #'--windowed', # prevent console appearing, only use with ui.run(native=True, ...)
+                            '--add-data', f'{Path(nicegui.__file__).parent}{os.pathsep}nicegui'
+                        ]
+                        subprocess.call(cmd)
+                        ```
+                    ''')
+
+        self.text('', '''
+            **Packaging Tips:**
+
+            - When building a PyInstaller app, your main script can use a native window (rather than a browser window) by
+            using `ui.run(reload=False, native=True)`.
+            The `native` parameter can be `True` or `False` depending on whether you want a native window or to launch a
+            page in the user's browser - either will work in the PyInstaller generated app.
+
+            - Specifying `--windowed` to PyInstaller will prevent a terminal console from appearing.
+            However you should only use this option if you have also specified `native=True` in your `ui.run` command.
+            Without a terminal console the user won't be able to exit the app by pressing Ctrl-C.
+            With the `native=True` option, the app will automatically close when the window is closed, as expected.
+
+            - Specifying `--windowed` to PyInstaller will create an `.app` file on Mac which may be more convenient to distribute.
+            When you double-click the app to run it, it will not show any console output.
+            You can also run the app from the command line with `./myapp.app/Contents/MacOS/myapp` to see the console output.
+
+            - Specifying `--onefile` to PyInstaller will create a single executable file.
+            Whilst convenient for distribution, it will be slower to start up.
+            This is not NiceGUI's fault but just the way Pyinstaller zips things into a single file, then unzips everything
+            into a temporary directory before running.
+            You can mitigate this by removing `--onefile` from the PyInstaller command,
+            and zip up the generated `dist` directory yourself, distribute it,
+            and your end users can unzip once and be good to go,
+            without the constant expansion of files due to the `--onefile` flag.
+            
+            - Summary of user experience for different options:
+
+                | PyInstaller              | `ui.run(...)`  | Explanation |
+                | :---                     | :---           | :---        |
+                | `onefile`                | `native=False` | Single executable generated in `dist/`, runs in browser |
+                | `onefile`                | `native=True`  | Single executable generated in `dist/`, runs in popup window |
+                | `onefile` and `windowed` | `native=True`  | Single executable generated in `dist/` (on Mac a proper `dist/myapp.app` generated incl. icon), runs in popup window, no console appears |
+                | `onefile` and `windowed` | `native=False` | Avoid (no way to exit the app) |
+                | Specify neither          |                | A `dist/myapp` directory created which can be zipped manually and distributed; run with `dist/myapp/myapp` |
+
+            - If you are using a Python virtual environment, ensure you `pip install pyinstaller` within your virtual environment
+            so that the correct PyInstaller is used, or you may get broken apps due to the wrong version of PyInstaller being picked up.
+            That is why the build script invokes PyInstaller using `python -m PyInstaller` rather than just `pyinstaller`.
+        ''')
+
+        @self.ui
+        def install_pyinstaller():
+            with bash_window(classes='max-w-lg w-full h-42 self-center'):
+                ui.markdown('''
+                    ```bash
+                    python -m venv venv
+                    source venv/bin/activate
+                    pip install nicegui
+                    pip install pyinstaller
+                    ```
+                ''')
+
+        self.text('', '''
+            **Note:**
+            If you're getting an error "TypeError: a bytes-like object is required, not 'str'", try adding the following lines to the top of your `main.py` file:
+            ```py
+            import sys
+            sys.stdout = open('logs.txt', 'w')
+            ```
+            See <https://github.com/zauberzeug/nicegui/issues/681> for more information.
+        ''')
+
+        self.text('NiceGUI On Air', '''
+            By using `ui.run(on_air=True)` you can share your local app with others over the internet 🧞.
+
+            When accessing the on-air URL, all libraries (like Vue, Quasar, ...) are loaded from our CDN.
+            Thereby only the raw content and events need to be transmitted by your local app.
+            This makes it blazing fast even if your app only has a poor internet connection (e.g. a mobile robot in the field).
+
+            By setting `on_air=True` you will get a random URL which is valid for 1 hour.
+            If you sign-up at <https://on-air.nicegui.io> you get a token which could be used to identify your device: `ui.run(on_air='<your token>'`).
+            This will give you a fixed URL and the possibility to protect remote access with a passphrase.
+
+            Currently On Air is available as a tech preview and can be used free of charge (for now).
+            We will gradually improve stability, introduce payment options and extend the service with multi-device management, remote terminal access and more.
+            Please let us know your feedback on [GitHub](https://github.com/zauberzeug/nicegui/discussions),
+            [Reddit](https://www.reddit.com/r/nicegui/), or [Discord](https://discord.gg/TEpFeAaF4f).
+
+            **Data Privacy:**
+            We take your privacy very serious.
+            NiceGUI On Air does not log or store any content of the relayed data.
+        ''')

+ 1 - 1
website/documentation/more/notify_documentation.py

@@ -19,7 +19,7 @@ class NotifyDocumentation(UiElementDocumentation, element=ui.notify):
 
         @self.demo('Multiline Notifications', '''
             To allow a notification text to span multiple lines, it is sufficient to set `multi_line=True`.
-            If manual newline breaks are required (e.g. `\n`), you need to define a CSS style and pass it to the notification as shown in the example.
+            If manual newline breaks are required (e.g. `\\n`), you need to define a CSS style and pass it to the notification as shown in the example.
         ''')
         def multiline():
             ui.html('<style>.multi-line-notification { white-space: pre-line; }</style>')

+ 55 - 51
website/documentation/more/run_documentation.py

@@ -1,65 +1,69 @@
 from nicegui import ui
 
-from ..tools import text_demo
+from ..model import DetailDocumentation
 
 
-def main_demo() -> None:
-    ui.label('page with custom title')
+class RunDocumentation(DetailDocumentation, title='ui.*run*', name='run'):
 
-    # ui.run(title='My App')
-main_demo.tab = 'My App'
+    def content(self) -> None:
+        @self.demo(ui.run)  # TODO: tab = 'My App'
+        def demo() -> None:
+            ui.label('page with custom title')
 
+            # ui.run(title='My App')
 
-def more() -> None:
-    @text_demo('Emoji favicon', '''
-        You can use an emoji as favicon.
-        This works in Chrome, Firefox and Safari.
-    ''', tab=lambda: ui.markdown('🚀&nbsp; NiceGUI'))
-    def emoji_favicon():
-        ui.label('NiceGUI Rocks!')
+        @self.demo('Emoji favicon', '''
+            You can use an emoji as favicon.
+            This works in Chrome, Firefox and Safari.
+        ''')  # TODO: tab=lambda: ui.markdown('🚀&nbsp; NiceGUI')
+        def emoji_favicon():
+            ui.label('NiceGUI Rocks!')
 
-        # ui.run(favicon='🚀')
+            # ui.run(favicon='🚀')
 
-    @text_demo(
-        'Base64 favicon', '''
-        You can also use an base64-encoded image as favicon.
-    ''', tab=lambda: (
-            ui.image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')
-            .classes('w-4 h-4'),
-            ui.label('NiceGUI'),
-        ),
-    )
-    def base64_favicon():
-        ui.label('NiceGUI with a red dot!')
+        @self.demo(
+            'Base64 favicon', '''
+            You can also use an base64-encoded image as favicon.
+        ''')
+        # TODO
+        # tab=lambda: (
+        #     ui.image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')
+        #     .classes('w-4 h-4'),
+        #     ui.label('NiceGUI'),
+        # )
+        def base64_favicon():
+            ui.label('NiceGUI with a red dot!')
 
-        icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
+            icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
 
-        # ui.run(favicon=icon)
+            # ui.run(favicon=icon)
 
-    @text_demo('SVG favicon', '''
-        And directly use an SVG as favicon.
-        Works in Chrome, Firefox and Safari.
-    ''', tab=lambda: (
-        ui.html('''
-            <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
-                <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
-                <circle cx="80" cy="85" r="8" />
-                <circle cx="120" cy="85" r="8" />
-                <path d="m60,120 C75,150 125,150 140,120" style="fill:none; stroke:black; stroke-width:8; stroke-linecap:round" />
-            </svg>
-        ''').classes('w-4 h-4'),
-        ui.label('NiceGUI'),
-    ))
-    def svg_favicon():
-        ui.label('NiceGUI makes you smile!')
+        @self.demo('SVG favicon', '''
+            And directly use an SVG as favicon.
+            Works in Chrome, Firefox and Safari.
+        ''')
+        # TODO
+        # tab=lambda: (
+        #     ui.html('''
+        #         <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+        #             <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
+        #             <circle cx="80" cy="85" r="8" />
+        #             <circle cx="120" cy="85" r="8" />
+        #             <path d="m60,120 C75,150 125,150 140,120" style="fill:none; stroke:black; stroke-width:8; stroke-linecap:round" />
+        #         </svg>
+        #     ''').classes('w-4 h-4'),
+        #     ui.label('NiceGUI'),
+        # )
+        def svg_favicon():
+            ui.label('NiceGUI makes you smile!')
 
-        smiley = '''
-            <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
-                <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
-                <circle cx="80" cy="85" r="8" />
-                <circle cx="120" cy="85" r="8" />
-                <path d="m60,120 C75,150 125,150 140,120" style="fill:none; stroke:black; stroke-width:8; stroke-linecap:round" />
-            </svg>
-        '''
+            smiley = '''
+                <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+                    <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
+                    <circle cx="80" cy="85" r="8" />
+                    <circle cx="120" cy="85" r="8" />
+                    <path d="m60,120 C75,150 125,150 140,120" style="fill:none; stroke:black; stroke-width:8; stroke-linecap:round" />
+                </svg>
+            '''
 
-        # ui.run(favicon=smiley)
+            # ui.run(favicon=smiley)