zrb 1.0.0a16__py3-none-any.whl → 1.0.0a20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zrb/__init__.py +5 -0
- zrb/__main__.py +3 -0
- zrb/builtin/__init__.py +2 -2
- zrb/builtin/git.py +10 -2
- zrb/builtin/git_subtree.py +4 -0
- zrb/builtin/llm/tool/rag.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_input.py +16 -0
- zrb/builtin/project/add/fastapp/fastapp_task.py +78 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.flake8 +3 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/create_column_task.py +14 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +128 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +213 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/any_client_method.py +27 -0
- zrb/builtin/project/add/{fastapp_template/_zrb/entity/module_template → fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module}/service/my_entity/my_entity_usecase.py +9 -10
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/factory.py +13 -0
- zrb/builtin/project/add/{fastapp_template/_zrb/entity/module_template → fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module}/service/my_entity/repository/my_entity_db_repository.py +14 -9
- zrb/builtin/project/add/{fastapp_template/_zrb/entity/module_template → fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module}/service/my_entity/repository/my_entity_repository.py +6 -7
- zrb/builtin/project/add/{fastapp_template/_zrb/entity/schema.template.py → fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py} +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +17 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/_zrb/input.py +1 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +85 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +154 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/any_client.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +9 -0
- zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/migration/env.py +2 -4
- zrb/builtin/project/add/{fastapp_template/module/auth → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/migration/script.py.mako +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +33 -0
- zrb/builtin/project/add/{fastapp_template/_zrb/main.py → fastapp/fastapp_template/my_app_name/_zrb/task.py} +12 -14
- zrb/builtin/project/add/{fastapp_template/_zrb/helper.py → fastapp/fastapp_template/my_app_name/_zrb/util.py} +1 -1
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/_zrb/venv_task.py +1 -1
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/app.py +2 -2
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/base_db_repository.py +1 -1
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/base_usecase.py +19 -6
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/db_engine.py +1 -1
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/config.py +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/main.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +3 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/client/any_client.py +10 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +7 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/factory.py +9 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/migration/env.py +2 -4
- zrb/builtin/project/add/{fastapp_template/module/gateway → fastapp/fastapp_template/my_app_name/module/auth}/migration/script.py.mako +1 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/migration_metadata.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +37 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/service/user/repository/user_db_repository.py +13 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +42 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository_factory.py +13 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/service/user/user_usecase.py +13 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +6 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/gateway/migration/env.py +2 -4
- zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/module/gateway}/migration/script.py.mako +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +37 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +44 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/requirements.txt +1 -1
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/schema/permission.py +8 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/schema/role.py +8 -0
- zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/schema/user.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_util.py +46 -0
- zrb/builtin/project/create/{create.py → project_task.py} +1 -1
- zrb/builtin/python.py +4 -1
- zrb/builtin/setup/asdf/asdf_helper.py +4 -8
- zrb/builtin/setup/tmux/tmux.py +7 -12
- zrb/builtin/todo.py +42 -26
- zrb/callback/callback.py +0 -1
- zrb/cmd/cmd_val.py +2 -2
- zrb/config.py +18 -0
- zrb/content_transformer/content_transformer.py +8 -7
- zrb/context/any_context.py +6 -6
- zrb/group/group.py +0 -1
- zrb/input/any_input.py +4 -0
- zrb/input/base_input.py +17 -5
- zrb/input/bool_input.py +1 -1
- zrb/input/float_input.py +2 -2
- zrb/input/int_input.py +1 -1
- zrb/input/option_input.py +2 -2
- zrb/input/password_input.py +2 -2
- zrb/input/text_input.py +6 -6
- zrb/runner/cli.py +9 -35
- zrb/runner/common_util.py +31 -0
- zrb/runner/web_app.py +169 -46
- zrb/runner/web_config.py +288 -0
- zrb/runner/web_controller/error_page/controller.py +27 -0
- zrb/runner/web_controller/error_page/view.html +33 -0
- zrb/runner/web_controller/group_info_page/controller.py +40 -0
- zrb/runner/web_controller/group_info_page/view.html +36 -0
- zrb/runner/web_controller/home_page/controller.py +14 -57
- zrb/runner/web_controller/home_page/view.html +29 -20
- zrb/runner/web_controller/login_page/controller.py +25 -0
- zrb/runner/web_controller/login_page/view.html +50 -0
- zrb/runner/web_controller/logout_page/controller.py +26 -0
- zrb/runner/web_controller/logout_page/view.html +40 -0
- zrb/runner/web_controller/{task_ui → session_page}/controller.py +36 -34
- zrb/runner/web_controller/{task_ui → session_page}/partial/input.html +1 -1
- zrb/runner/web_controller/session_page/view.html +91 -0
- zrb/runner/web_controller/static/common.css +11 -0
- zrb/runner/web_controller/static/login/event.js +33 -0
- zrb/runner/web_controller/static/logout/event.js +20 -0
- zrb/runner/web_controller/static/pico.min.css +1 -1
- zrb/runner/web_controller/static/session/common-util.js +63 -0
- zrb/runner/web_controller/static/session/current-session.js +164 -0
- zrb/runner/web_controller/static/session/event.js +120 -0
- zrb/runner/web_controller/static/session/past-session.js +138 -0
- zrb/runner/web_util.py +53 -0
- zrb/session_state_logger/any_session_state_logger.py +0 -1
- zrb/session_state_logger/file_session_state_logger.py +4 -8
- zrb/task/base_trigger.py +0 -1
- zrb/task/cmd_task.py +1 -1
- zrb/task/llm_task.py +3 -6
- zrb/task/make_task.py +0 -1
- zrb/task/scaffolder.py +18 -4
- zrb/task/scheduler.py +0 -1
- zrb/util/cmd/command.py +0 -1
- zrb/util/codemod/{add_code_to_class.py → append_code_to_class.py} +4 -4
- zrb/util/codemod/{add_code_to_function.py → append_code_to_function.py} +5 -3
- zrb/util/codemod/{add_code_to_method.py → append_code_to_method.py} +3 -3
- zrb/util/codemod/{add_key_to_dict.py → append_key_to_dict.py} +1 -1
- zrb/util/codemod/{add_param_to_function_call.py → append_param_to_function_call.py} +1 -1
- zrb/util/codemod/{add_code_to_module.py → prepend_code_to_module.py} +2 -2
- zrb/util/codemod/{add_parent_to_class.py → prepend_parent_to_class.py} +1 -1
- zrb/util/codemod/{add_property_to_class.py → prepend_property_to_class.py} +1 -1
- zrb/util/file.py +18 -0
- zrb/util/git_subtree.py +3 -4
- zrb/util/todo.py +105 -24
- zrb/xcom/xcom.py +0 -1
- {zrb-1.0.0a16.dist-info → zrb-1.0.0a20.dist-info}/METADATA +3 -2
- zrb-1.0.0a20.dist-info/RECORD +243 -0
- zrb/builtin/project/add/fastapp.py +0 -87
- zrb/builtin/project/add/fastapp_template/_zrb/column/create_column_task.py +0 -11
- zrb/builtin/project/add/fastapp_template/_zrb/entity/create_entity_task.py +0 -196
- zrb/builtin/project/add/fastapp_template/_zrb/entity/module_template/service/my_entity/repository/factory.py +0 -13
- zrb/builtin/project/add/fastapp_template/_zrb/module/create_module_task.py +0 -136
- zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/any_client.py +0 -27
- zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/api_client.py +0 -6
- zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/direct_client.py +0 -6
- zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/client/factory.py +0 -9
- zrb/builtin/project/add/fastapp_template/_zrb/module/module_template/route.py +0 -19
- zrb/builtin/project/add/fastapp_template/main.py +0 -7
- zrb/builtin/project/add/fastapp_template/migrate.py +0 -3
- zrb/builtin/project/add/fastapp_template/module/auth/client/api_client.py +0 -7
- zrb/builtin/project/add/fastapp_template/module/auth/client/direct_client.py +0 -6
- zrb/builtin/project/add/fastapp_template/module/auth/client/factory.py +0 -9
- zrb/builtin/project/add/fastapp_template/module/auth/migration/versions/3093c7336477_add_user_table.py +0 -37
- zrb/builtin/project/add/fastapp_template/module/auth/route.py +0 -22
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/factory.py +0 -13
- zrb/builtin/project/add/fastapp_template/module/auth/service/user/repository/user_repository.py +0 -34
- zrb/builtin/project/add/fastapp_template/module/gateway/route.py +0 -27
- zrb/runner/web_controller/group_info_ui/controller.py +0 -91
- zrb/runner/web_controller/group_info_ui/partial/group_info.html +0 -2
- zrb/runner/web_controller/group_info_ui/partial/group_li.html +0 -1
- zrb/runner/web_controller/group_info_ui/partial/task_info.html +0 -2
- zrb/runner/web_controller/group_info_ui/partial/task_li.html +0 -1
- zrb/runner/web_controller/group_info_ui/view.html +0 -31
- zrb/runner/web_controller/home_page/partial/group_info.html +0 -2
- zrb/runner/web_controller/home_page/partial/group_li.html +0 -1
- zrb/runner/web_controller/home_page/partial/task_info.html +0 -2
- zrb/runner/web_controller/home_page/partial/task_li.html +0 -1
- zrb/runner/web_controller/task_ui/partial/common-util.js +0 -37
- zrb/runner/web_controller/task_ui/partial/main.js +0 -195
- zrb/runner/web_controller/task_ui/partial/show-existing-session.js +0 -97
- zrb/runner/web_controller/task_ui/partial/visualize-history.js +0 -104
- zrb/runner/web_controller/task_ui/view.html +0 -87
- zrb-1.0.0a16.dist-info/RECORD +0 -231
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/.gitignore +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/README.md +0 -0
- /zrb/builtin/project/add/{__init__.py → fastapp/fastapp_template/my_app_name/__init__.py} +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/_zrb/config.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/_zrb/group.py +0 -0
- /zrb/builtin/project/add/{fastapp_template/__init__.py → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py} +0 -0
- /zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/alembic.ini +0 -0
- /zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/migration/README +0 -0
- /zrb/builtin/project/add/{fastapp_template/module/gateway → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/migration/versions/.gitkeep +0 -0
- /zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/migration_metadata.py +0 -0
- /zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module}/service/__init__.py +0 -0
- /zrb/builtin/project/add/{fastapp_template/_zrb/module/run_module.template.py → fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py} +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/__init__.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/error.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/common/schema.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/__init__.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/alembic.ini +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/migration/README +0 -0
- /zrb/builtin/project/add/{fastapp_template/_zrb/module/module_template → fastapp/fastapp_template/my_app_name/module/auth}/migration/versions/3093c7336477_add_user_table.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/service/__init__.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/auth/service/user/__init__.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/gateway/alembic.ini +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/gateway/migration/README +0 -0
- /zrb/builtin/project/add/{fastapp_template/module/auth/service/user/repository/__init__.py → fastapp/fastapp_template/my_app_name/module/gateway/migration/versions/.gitkeep} +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/module/gateway/migration_metadata.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/schema/__init__.py +0 -0
- /zrb/builtin/project/add/{fastapp_template → fastapp/fastapp_template/my_app_name}/template.env +0 -0
- /zrb/runner/web_controller/{group_info_ui → group_info_page}/__init__.py +0 -0
- /zrb/runner/web_controller/{task_ui → session_page}/__init__.py +0 -0
- {zrb-1.0.0a16.dist-info → zrb-1.0.0a20.dist-info}/WHEEL +0 -0
- {zrb-1.0.0a16.dist-info → zrb-1.0.0a20.dist-info}/entry_points.txt +0 -0
zrb/runner/web_app.py
CHANGED
@@ -1,30 +1,44 @@
|
|
1
1
|
import asyncio
|
2
|
+
import json
|
2
3
|
import os
|
3
4
|
import sys
|
4
5
|
from datetime import datetime, timedelta
|
5
|
-
from typing import
|
6
|
+
from typing import TYPE_CHECKING, Annotated
|
6
7
|
|
7
|
-
from zrb.config import BANNER,
|
8
|
+
from zrb.config import BANNER, VERSION
|
8
9
|
from zrb.context.shared_context import SharedContext
|
9
10
|
from zrb.group.any_group import AnyGroup
|
10
|
-
from zrb.runner.
|
11
|
-
from zrb.runner.
|
12
|
-
from zrb.runner.web_controller.
|
11
|
+
from zrb.runner.common_util import get_run_kwargs
|
12
|
+
from zrb.runner.web_config import Token, WebConfig
|
13
|
+
from zrb.runner.web_controller.error_page.controller import show_error_page
|
14
|
+
from zrb.runner.web_controller.group_info_page.controller import show_group_info_page
|
15
|
+
from zrb.runner.web_controller.home_page.controller import show_home_page
|
16
|
+
from zrb.runner.web_controller.login_page.controller import show_login_page
|
17
|
+
from zrb.runner.web_controller.logout_page.controller import show_logout_page
|
18
|
+
from zrb.runner.web_controller.session_page.controller import show_session_page
|
13
19
|
from zrb.runner.web_util import NewSessionResponse
|
14
20
|
from zrb.session.session import Session
|
15
21
|
from zrb.session_state_log.session_state_log import SessionStateLog, SessionStateLogList
|
16
|
-
from zrb.session_state_logger.
|
17
|
-
default_session_state_logger,
|
18
|
-
)
|
22
|
+
from zrb.session_state_logger.any_session_state_logger import AnySessionStateLogger
|
19
23
|
from zrb.task.any_task import AnyTask
|
20
|
-
from zrb.util.group import extract_node_from_args, get_node_path
|
24
|
+
from zrb.util.group import NodeNotFoundError, extract_node_from_args, get_node_path
|
21
25
|
|
26
|
+
if TYPE_CHECKING:
|
27
|
+
# We want fastapi to only be loaded when necessary to decrease footprint
|
28
|
+
from fastapi import FastAPI
|
22
29
|
|
23
|
-
|
30
|
+
|
31
|
+
def create_app(
|
32
|
+
root_group: AnyGroup,
|
33
|
+
web_config: WebConfig,
|
34
|
+
session_state_logger: AnySessionStateLogger,
|
35
|
+
) -> "FastAPI":
|
24
36
|
from contextlib import asynccontextmanager
|
25
37
|
|
26
|
-
from fastapi import FastAPI, HTTPException, Query, Request
|
38
|
+
from fastapi import Depends, FastAPI, HTTPException, Query, Request, Response
|
39
|
+
from fastapi.openapi.docs import get_swagger_ui_html
|
27
40
|
from fastapi.responses import FileResponse, HTMLResponse
|
41
|
+
from fastapi.security import OAuth2PasswordRequestForm
|
28
42
|
from fastapi.staticfiles import StaticFiles
|
29
43
|
|
30
44
|
_STATIC_DIR = os.path.join(os.path.dirname(__file__), "web_controller", "static")
|
@@ -33,7 +47,7 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
|
33
47
|
@asynccontextmanager
|
34
48
|
async def lifespan(app: FastAPI):
|
35
49
|
for line in BANNER.split("\n") + [
|
36
|
-
f"Zrb Server running on http://localhost:{port}"
|
50
|
+
f"Zrb Server running on http://localhost:{web_config.port}"
|
37
51
|
]:
|
38
52
|
print(line, file=sys.stderr)
|
39
53
|
yield
|
@@ -41,17 +55,16 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
|
41
55
|
coro.cancel()
|
42
56
|
asyncio.gather(*_COROS)
|
43
57
|
|
44
|
-
app = FastAPI(
|
45
|
-
|
46
|
-
|
58
|
+
app = FastAPI(
|
59
|
+
title="Zrb",
|
60
|
+
version=VERSION,
|
61
|
+
summary="Your Automation Powerhouse",
|
62
|
+
lifespan=lifespan,
|
63
|
+
docs_url=None,
|
64
|
+
)
|
47
65
|
app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
|
48
66
|
|
49
|
-
|
50
|
-
@app.get("/ui", response_class=HTMLResponse, include_in_schema=False)
|
51
|
-
@app.get("/ui/", response_class=HTMLResponse, include_in_schema=False)
|
52
|
-
async def home_page():
|
53
|
-
return handle_home_page(root_group)
|
54
|
-
|
67
|
+
# Serve static files
|
55
68
|
@app.get("/static/{file_path:path}", include_in_schema=False)
|
56
69
|
async def static_files(file_path: str):
|
57
70
|
full_path = os.path.join(_STATIC_DIR, file_path)
|
@@ -59,59 +72,169 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
|
59
72
|
return FileResponse(full_path)
|
60
73
|
raise HTTPException(status_code=404, detail="File not found")
|
61
74
|
|
62
|
-
@app.get("/
|
63
|
-
async def
|
75
|
+
@app.get("/docs", include_in_schema=False)
|
76
|
+
async def swagger_ui_html():
|
77
|
+
return get_swagger_ui_html(
|
78
|
+
openapi_url="/openapi.json",
|
79
|
+
title="Zrb",
|
80
|
+
swagger_favicon_url="/static/favicon-32x32.png",
|
81
|
+
)
|
82
|
+
|
83
|
+
# Serve homepage
|
84
|
+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
85
|
+
@app.get("/ui", response_class=HTMLResponse, include_in_schema=False)
|
86
|
+
@app.get("/ui/", response_class=HTMLResponse, include_in_schema=False)
|
87
|
+
async def home_page_ui(request: Request) -> HTMLResponse:
|
88
|
+
user = await web_config.get_user_by_request(request)
|
89
|
+
return show_home_page(user, root_group)
|
90
|
+
|
91
|
+
@app.get("/ui/{path:path}", response_class=HTMLResponse, include_in_schema=False)
|
92
|
+
async def ui_page(path: str, request: Request) -> HTMLResponse:
|
93
|
+
user = await web_config.get_user_by_request(request)
|
64
94
|
# Avoid capturing '/ui' itself
|
65
95
|
if not path:
|
66
|
-
|
96
|
+
return show_error_page(user, root_group, 422, "Undefined path")
|
67
97
|
args = path.strip("/").split("/")
|
68
|
-
|
98
|
+
try:
|
99
|
+
node, node_path, residual_args = extract_node_from_args(root_group, args)
|
100
|
+
except NodeNotFoundError as e:
|
101
|
+
return show_error_page(user, root_group, 404, str(e))
|
69
102
|
url = f"/ui/{'/'.join(node_path)}/"
|
70
103
|
if isinstance(node, AnyTask):
|
104
|
+
if not user.can_access_task(node):
|
105
|
+
return show_error_page(user, root_group, 403, "Forbidden")
|
71
106
|
shared_ctx = SharedContext(env=dict(os.environ))
|
72
107
|
session = Session(shared_ctx=shared_ctx, root_group=root_group)
|
73
|
-
return
|
108
|
+
return show_session_page(
|
109
|
+
user, root_group, node, session, url, residual_args
|
110
|
+
)
|
74
111
|
elif isinstance(node, AnyGroup):
|
75
|
-
|
76
|
-
|
112
|
+
if not user.can_access_group(node):
|
113
|
+
return show_error_page(user, root_group, 403, "Forbidden")
|
114
|
+
return show_group_info_page(user, root_group, node, url)
|
115
|
+
return show_error_page(user, root_group, 404, "Not found")
|
116
|
+
|
117
|
+
@app.get("/login", response_class=HTMLResponse, include_in_schema=False)
|
118
|
+
async def login(request: Request) -> HTMLResponse:
|
119
|
+
user = await web_config.get_user_by_request(request)
|
120
|
+
return show_login_page(user, root_group)
|
77
121
|
|
78
|
-
@app.
|
79
|
-
async def
|
80
|
-
|
122
|
+
@app.get("/logout", response_class=HTMLResponse, include_in_schema=False)
|
123
|
+
async def logout(request: Request) -> HTMLResponse:
|
124
|
+
user = await web_config.get_user_by_request(request)
|
125
|
+
return show_logout_page(user, root_group)
|
126
|
+
|
127
|
+
@app.post("/api/v1/login")
|
128
|
+
async def login_api(
|
129
|
+
response: Response, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
|
130
|
+
):
|
131
|
+
token = web_config.generate_tokens(
|
132
|
+
username=form_data.username, password=form_data.password
|
133
|
+
)
|
134
|
+
_set_auth_cookie(response, token)
|
135
|
+
return token
|
136
|
+
|
137
|
+
@app.post("/api/v1/refresh-token")
|
138
|
+
async def refresh_token_api(
|
139
|
+
response: Response, refresh_token: str = Query(..., description="Refresh token")
|
140
|
+
):
|
141
|
+
token = web_config.refresh_tokens(refresh_token)
|
142
|
+
_set_auth_cookie(response, token)
|
143
|
+
return token
|
144
|
+
|
145
|
+
def _set_auth_cookie(response: Response, token: Token):
|
146
|
+
response.set_cookie(
|
147
|
+
key=web_config.access_token_cookie_name,
|
148
|
+
value=token.access_token,
|
149
|
+
httponly=True,
|
150
|
+
max_age=web_config.access_token_max_age,
|
151
|
+
expires=web_config.access_token_max_age,
|
152
|
+
)
|
153
|
+
response.set_cookie(
|
154
|
+
key=web_config.refresh_token_cookie_name,
|
155
|
+
value=token.refresh_token,
|
156
|
+
httponly=True,
|
157
|
+
max_age=web_config.refresh_token_max_age,
|
158
|
+
expires=web_config.refresh_token_max_age,
|
159
|
+
)
|
160
|
+
|
161
|
+
@app.get("/api/v1/logout")
|
162
|
+
@app.post("/api/v1/logout")
|
163
|
+
async def logout_api(response: Response):
|
164
|
+
response.delete_cookie(web_config.access_token_cookie_name)
|
165
|
+
response.delete_cookie(web_config.refresh_token_cookie_name)
|
166
|
+
return {"message": "Logout successful"}
|
167
|
+
|
168
|
+
@app.post("/api/sessions/{path:path}")
|
169
|
+
async def create_new_session_api(
|
170
|
+
path: str,
|
171
|
+
request: Request,
|
81
172
|
) -> NewSessionResponse:
|
82
173
|
"""
|
83
174
|
Creating new session
|
84
175
|
"""
|
176
|
+
user = await web_config.get_user_by_request(request)
|
85
177
|
args = path.strip("/").split("/")
|
86
|
-
|
87
|
-
if isinstance(
|
178
|
+
task, _, residual_args = extract_node_from_args(root_group, args)
|
179
|
+
if isinstance(task, AnyTask):
|
180
|
+
if not user.can_access_task(task):
|
181
|
+
raise HTTPException(status_code=403)
|
88
182
|
session_name = residual_args[0] if residual_args else None
|
89
183
|
if not session_name:
|
90
184
|
body = await request.json()
|
91
185
|
shared_ctx = SharedContext(env=dict(os.environ))
|
92
186
|
session = Session(shared_ctx=shared_ctx, root_group=root_group)
|
93
|
-
coro = asyncio.create_task(
|
187
|
+
coro = asyncio.create_task(task.async_run(session, str_kwargs=body))
|
94
188
|
_COROS.append(coro)
|
95
189
|
coro.add_done_callback(lambda coro: _COROS.remove(coro))
|
96
190
|
return NewSessionResponse(session_name=session.name)
|
191
|
+
raise HTTPException(status_code=404)
|
192
|
+
|
193
|
+
@app.get("/api/inputs/{path:path}", response_model=dict[str, str])
|
194
|
+
async def get_default_inputs_api(
|
195
|
+
path: str,
|
196
|
+
request: Request,
|
197
|
+
query: str = Query("{}", description="JSON encoded inputs"),
|
198
|
+
) -> dict[str, str]:
|
199
|
+
"""
|
200
|
+
Getting input completion for path
|
201
|
+
"""
|
202
|
+
user = await web_config.get_user_by_request(request)
|
203
|
+
args = path.strip("/").split("/")
|
204
|
+
task, _, _ = extract_node_from_args(root_group, args)
|
205
|
+
if isinstance(task, AnyTask):
|
206
|
+
if not user.can_access_task(task):
|
207
|
+
raise HTTPException(status_code=403)
|
208
|
+
query_dict = json.loads(query)
|
209
|
+
run_kwargs = get_run_kwargs(
|
210
|
+
task=task, args=[], kwargs=query_dict, prompt=False
|
211
|
+
)
|
212
|
+
return run_kwargs
|
97
213
|
raise HTTPException(status_code=404, detail="Not Found")
|
98
214
|
|
99
|
-
@app.get(
|
100
|
-
|
215
|
+
@app.get(
|
216
|
+
"/api/sessions/{path:path}",
|
217
|
+
response_model=SessionStateLog | SessionStateLogList,
|
218
|
+
)
|
219
|
+
async def get_session_api(
|
101
220
|
path: str,
|
221
|
+
request: Request,
|
102
222
|
min_start_query: str = Query(default=None, alias="from"),
|
103
223
|
max_start_query: str = Query(default=None, alias="to"),
|
104
224
|
page: int = Query(default=0, alias="page"),
|
105
225
|
limit: int = Query(default=10, alias="limit"),
|
106
|
-
):
|
226
|
+
) -> SessionStateLog | SessionStateLogList:
|
107
227
|
"""
|
108
228
|
Getting existing session or sessions
|
109
229
|
"""
|
230
|
+
user = await web_config.get_user_by_request(request)
|
110
231
|
args = path.strip("/").split("/")
|
111
|
-
|
112
|
-
if isinstance(
|
232
|
+
task, _, residual_args = extract_node_from_args(root_group, args)
|
233
|
+
if isinstance(task, AnyTask) and residual_args:
|
234
|
+
if not user.can_access_task(task):
|
235
|
+
raise HTTPException(status_code=403)
|
113
236
|
if residual_args[0] == "list":
|
114
|
-
task_path = get_node_path(root_group,
|
237
|
+
task_path = get_node_path(root_group, task)
|
115
238
|
max_start_time = (
|
116
239
|
datetime.now()
|
117
240
|
if max_start_query is None
|
@@ -122,14 +245,14 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
|
122
245
|
if min_start_query is None
|
123
246
|
else datetime.strptime(min_start_query, "%Y-%m-%d %H:%M:%S")
|
124
247
|
)
|
125
|
-
return
|
248
|
+
return _get_existing_sessions(
|
126
249
|
task_path, min_start_time, max_start_time, page, limit
|
127
250
|
)
|
128
251
|
else:
|
129
|
-
return
|
252
|
+
return _read_session(residual_args[0])
|
130
253
|
raise HTTPException(status_code=404, detail="Not Found")
|
131
254
|
|
132
|
-
def
|
255
|
+
def _get_existing_sessions(
|
133
256
|
task_path: list[str],
|
134
257
|
min_start_time: datetime,
|
135
258
|
max_start_time: datetime,
|
@@ -137,7 +260,7 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
|
137
260
|
limit: int,
|
138
261
|
) -> SessionStateLogList:
|
139
262
|
try:
|
140
|
-
return
|
263
|
+
return session_state_logger.list(
|
141
264
|
task_path,
|
142
265
|
min_start_time=min_start_time,
|
143
266
|
max_start_time=max_start_time,
|
@@ -147,9 +270,9 @@ def create_app(root_group: AnyGroup, port: int = WEB_HTTP_PORT):
|
|
147
270
|
except Exception as e:
|
148
271
|
raise HTTPException(status_code=500, detail=str(e))
|
149
272
|
|
150
|
-
def
|
273
|
+
def _read_session(session_name: str) -> SessionStateLog:
|
151
274
|
try:
|
152
|
-
return
|
275
|
+
return session_state_logger.read(session_name)
|
153
276
|
except Exception as e:
|
154
277
|
raise HTTPException(status_code=500, detail=str(e))
|
155
278
|
|
zrb/runner/web_config.py
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
from typing import TYPE_CHECKING, Callable
|
3
|
+
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
5
|
+
|
6
|
+
from zrb.config import (
|
7
|
+
WEB_ACCESS_TOKEN_COOKIE_NAME,
|
8
|
+
WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
|
9
|
+
WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
|
10
|
+
WEB_ENABLE_AUTH,
|
11
|
+
WEB_GUEST_USERNAME,
|
12
|
+
WEB_HTTP_PORT,
|
13
|
+
WEB_REFRESH_TOKEN_COOKIE_NAME,
|
14
|
+
WEB_SECRET_KEY,
|
15
|
+
WEB_SUPER_ADMIN_PASSWORD,
|
16
|
+
WEB_SUPER_ADMIN_USERNAME,
|
17
|
+
)
|
18
|
+
from zrb.group.any_group import AnyGroup
|
19
|
+
from zrb.task.any_task import AnyTask
|
20
|
+
from zrb.util.group import get_all_subtasks
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
# Import Request only for type checking to reduce runtime dependencies
|
24
|
+
from fastapi import Request
|
25
|
+
|
26
|
+
|
27
|
+
class User(BaseModel):
|
28
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
29
|
+
username: str
|
30
|
+
password: str = ""
|
31
|
+
is_super_admin: bool = False
|
32
|
+
is_guest: bool = False
|
33
|
+
accessible_tasks: list[AnyTask | str] = []
|
34
|
+
|
35
|
+
def is_password_match(self, password: str) -> bool:
|
36
|
+
return self.password == password
|
37
|
+
|
38
|
+
def can_access_group(self, group: AnyGroup) -> bool:
|
39
|
+
if self.is_super_admin:
|
40
|
+
return True
|
41
|
+
all_tasks = get_all_subtasks(group, web_only=True)
|
42
|
+
if any(self.can_access_task(task) for task in all_tasks):
|
43
|
+
return True
|
44
|
+
return False
|
45
|
+
|
46
|
+
def can_access_task(self, task: AnyTask) -> bool:
|
47
|
+
if self.is_super_admin:
|
48
|
+
return True
|
49
|
+
if task.name in self.accessible_tasks or task in self.accessible_tasks:
|
50
|
+
return True
|
51
|
+
return False
|
52
|
+
|
53
|
+
|
54
|
+
class Token(BaseModel):
|
55
|
+
access_token: str
|
56
|
+
refresh_token: str
|
57
|
+
token_type: str
|
58
|
+
|
59
|
+
|
60
|
+
class WebConfig:
|
61
|
+
def __init__(
|
62
|
+
self,
|
63
|
+
port: int,
|
64
|
+
secret_key: str,
|
65
|
+
access_token_expire_minutes: int,
|
66
|
+
refresh_token_expire_minutes: int,
|
67
|
+
access_token_cookie_name: str,
|
68
|
+
refresh_token_cookie_name: str,
|
69
|
+
enable_auth: bool,
|
70
|
+
super_admin_username: str,
|
71
|
+
super_admin_password: str,
|
72
|
+
guest_username: str,
|
73
|
+
guest_accessible_tasks: list[AnyTask | str] = [],
|
74
|
+
find_user_by_username: Callable[[str], User | None] | None = None,
|
75
|
+
):
|
76
|
+
self._secret_key = secret_key
|
77
|
+
self._access_token_expire_minutes = access_token_expire_minutes
|
78
|
+
self._refresh_token_expire_minutes = refresh_token_expire_minutes
|
79
|
+
self._access_token_cookie_name = access_token_cookie_name
|
80
|
+
self._refresh_token_cookie_name = refresh_token_cookie_name
|
81
|
+
self._enable_auth = enable_auth
|
82
|
+
self._port = port
|
83
|
+
self._user_list = []
|
84
|
+
self._super_admin_username = super_admin_username
|
85
|
+
self._super_admin_password = super_admin_password
|
86
|
+
self._guest_username = guest_username
|
87
|
+
self._guest_accessible_tasks = guest_accessible_tasks
|
88
|
+
self._find_user_by_username = find_user_by_username
|
89
|
+
|
90
|
+
@property
|
91
|
+
def port(self) -> int:
|
92
|
+
return self._port
|
93
|
+
|
94
|
+
@property
|
95
|
+
def access_token_cookie_name(self) -> str:
|
96
|
+
return self._access_token_cookie_name
|
97
|
+
|
98
|
+
@property
|
99
|
+
def refresh_token_cookie_name(self) -> str:
|
100
|
+
return self._refresh_token_cookie_name
|
101
|
+
|
102
|
+
@property
|
103
|
+
def access_token_max_age(self) -> int:
|
104
|
+
self._access_token_expire_minutes * 60
|
105
|
+
|
106
|
+
@property
|
107
|
+
def refresh_token_max_age(self) -> int:
|
108
|
+
self._refresh_token_expire_minutes * 60
|
109
|
+
|
110
|
+
@property
|
111
|
+
def default_user(self) -> User:
|
112
|
+
if self._enable_auth:
|
113
|
+
return User(
|
114
|
+
username=self._guest_username,
|
115
|
+
password="",
|
116
|
+
is_guest=True,
|
117
|
+
accessible_tasks=self._guest_accessible_tasks,
|
118
|
+
)
|
119
|
+
return User(
|
120
|
+
username=self._guest_username,
|
121
|
+
password="",
|
122
|
+
is_guest=True,
|
123
|
+
is_super_admin=True,
|
124
|
+
)
|
125
|
+
|
126
|
+
@property
|
127
|
+
def super_admin(self) -> User:
|
128
|
+
return User(
|
129
|
+
username=self._super_admin_username,
|
130
|
+
password=self._super_admin_password,
|
131
|
+
is_super_admin=True,
|
132
|
+
)
|
133
|
+
|
134
|
+
@property
|
135
|
+
def user_list(self) -> list[User]:
|
136
|
+
if not self._enable_auth:
|
137
|
+
return [self.default_user]
|
138
|
+
return self._user_list + [self.super_admin, self.default_user]
|
139
|
+
|
140
|
+
def set_guest_accessible_tasks(self, tasks: list[AnyTask | str]):
|
141
|
+
self._guest_accessible_tasks = tasks
|
142
|
+
|
143
|
+
def set_find_user_by_username(
|
144
|
+
self, find_user_by_username: Callable[[str], User | None]
|
145
|
+
):
|
146
|
+
self._find_user_by_username = find_user_by_username
|
147
|
+
|
148
|
+
def append_user(self, user: User):
|
149
|
+
duplicates = [
|
150
|
+
existing_user
|
151
|
+
for existing_user in self.user_list
|
152
|
+
if existing_user.username == user.username
|
153
|
+
]
|
154
|
+
if len(duplicates) > 0:
|
155
|
+
raise ValueError(f"User already exists {user.username}")
|
156
|
+
self._user_list.append(user)
|
157
|
+
|
158
|
+
def enable_auth(self):
|
159
|
+
self._enable_auth = True
|
160
|
+
|
161
|
+
def disable_auth(self):
|
162
|
+
self._enable_auth = False
|
163
|
+
|
164
|
+
def find_user_by_username(self, username: str) -> User | None:
|
165
|
+
user = None
|
166
|
+
if self._find_user_by_username is not None:
|
167
|
+
user = self._find_user_by_username(username)
|
168
|
+
if user is None:
|
169
|
+
user = next((u for u in self.user_list if u.username == username), None)
|
170
|
+
return user
|
171
|
+
|
172
|
+
async def get_user_by_request(self, request: "Request") -> User | None:
|
173
|
+
from fastapi.security import OAuth2PasswordBearer
|
174
|
+
|
175
|
+
if not self._enable_auth:
|
176
|
+
return self.default_user
|
177
|
+
# Normally we use "Depends"
|
178
|
+
get_bearer_token = OAuth2PasswordBearer(
|
179
|
+
tokenUrl="/api/v1/login", auto_error=False
|
180
|
+
)
|
181
|
+
bearer_token = await get_bearer_token(request)
|
182
|
+
token_user = self._get_user_from_token(bearer_token)
|
183
|
+
if token_user is not None:
|
184
|
+
return token_user
|
185
|
+
cookie_user = self._get_user_from_cookie(request)
|
186
|
+
if cookie_user is not None:
|
187
|
+
return cookie_user
|
188
|
+
return self.default_user
|
189
|
+
|
190
|
+
def _get_user_from_token(self, token: str) -> User | None:
|
191
|
+
try:
|
192
|
+
from jose import jwt
|
193
|
+
|
194
|
+
payload = jwt.decode(
|
195
|
+
token,
|
196
|
+
self._secret_key,
|
197
|
+
options={"require_sub": True, "require_exp": True},
|
198
|
+
)
|
199
|
+
username: str = payload.get("sub")
|
200
|
+
if username is None:
|
201
|
+
return None
|
202
|
+
user = self.find_user_by_username(username)
|
203
|
+
if user is None:
|
204
|
+
return None
|
205
|
+
return user
|
206
|
+
except Exception:
|
207
|
+
return None
|
208
|
+
|
209
|
+
def _get_user_from_cookie(self, request: "Request") -> User | None:
|
210
|
+
token = request.cookies.get(self._access_token_cookie_name)
|
211
|
+
if token:
|
212
|
+
return self._get_user_from_token(token)
|
213
|
+
return None
|
214
|
+
|
215
|
+
def get_user_by_credentials(self, username: str, password: str) -> User:
|
216
|
+
user = self.find_user_by_username(username)
|
217
|
+
if user is None or not user.is_password_match(password):
|
218
|
+
return self.default_user
|
219
|
+
return user
|
220
|
+
|
221
|
+
def generate_tokens(self, username: str, password: str) -> Token:
|
222
|
+
if not self._enable_auth:
|
223
|
+
user = self.default_user
|
224
|
+
else:
|
225
|
+
user = self.get_user_by_credentials(username, password)
|
226
|
+
access_token = self.create_access_token(user.username)
|
227
|
+
refresh_token = self.create_refresh_token(user.username)
|
228
|
+
return Token(
|
229
|
+
access_token=access_token, refresh_token=refresh_token, token_type="bearer"
|
230
|
+
)
|
231
|
+
|
232
|
+
def create_access_token(self, username: str) -> str:
|
233
|
+
from jose import jwt
|
234
|
+
|
235
|
+
expire = datetime.now() + timedelta(minutes=self._access_token_expire_minutes)
|
236
|
+
to_encode = {"sub": username, "exp": expire, "type": "access"}
|
237
|
+
return jwt.encode(to_encode, self._secret_key)
|
238
|
+
|
239
|
+
def create_refresh_token(self, username: str) -> str:
|
240
|
+
from jose import jwt
|
241
|
+
|
242
|
+
expire = datetime.now() + timedelta(minutes=self._refresh_token_expire_minutes)
|
243
|
+
to_encode = {"sub": username, "exp": expire, "type": "refresh"}
|
244
|
+
return jwt.encode(to_encode, self._secret_key)
|
245
|
+
|
246
|
+
def refresh_tokens(self, refresh_token: str) -> Token:
|
247
|
+
from fastapi import HTTPException
|
248
|
+
from jose import jwt
|
249
|
+
|
250
|
+
# Decode and validate token
|
251
|
+
try:
|
252
|
+
payload = jwt.decode(
|
253
|
+
refresh_token,
|
254
|
+
self._secret_key,
|
255
|
+
options={"require_exp": True, "require_sub": True},
|
256
|
+
)
|
257
|
+
except Exception:
|
258
|
+
raise HTTPException(status_code=401, detail="Invalid JWT token")
|
259
|
+
if payload.get("type") != "refresh":
|
260
|
+
raise HTTPException(status_code=401, detail="Invalid token type")
|
261
|
+
username: str = payload.get("sub")
|
262
|
+
if username is None:
|
263
|
+
raise HTTPException(status_code=401, detail="Invalid refresh token")
|
264
|
+
user = self.find_user_by_username(username)
|
265
|
+
if user is None:
|
266
|
+
raise HTTPException(status_code=401, detail="User not found")
|
267
|
+
# Create new token
|
268
|
+
new_access_token = self.create_access_token(username)
|
269
|
+
new_refresh_token = self.create_refresh_token(username)
|
270
|
+
return Token(
|
271
|
+
access_token=new_access_token,
|
272
|
+
refresh_token=new_refresh_token,
|
273
|
+
token_type="bearer",
|
274
|
+
)
|
275
|
+
|
276
|
+
|
277
|
+
web_config = WebConfig(
|
278
|
+
port=WEB_HTTP_PORT,
|
279
|
+
secret_key=WEB_SECRET_KEY,
|
280
|
+
access_token_expire_minutes=WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
|
281
|
+
refresh_token_expire_minutes=WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
|
282
|
+
access_token_cookie_name=WEB_ACCESS_TOKEN_COOKIE_NAME,
|
283
|
+
refresh_token_cookie_name=WEB_REFRESH_TOKEN_COOKIE_NAME,
|
284
|
+
enable_auth=WEB_ENABLE_AUTH,
|
285
|
+
super_admin_username=WEB_SUPER_ADMIN_USERNAME,
|
286
|
+
super_admin_password=WEB_SUPER_ADMIN_PASSWORD,
|
287
|
+
guest_username=WEB_GUEST_USERNAME,
|
288
|
+
)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from zrb.group.any_group import AnyGroup
|
4
|
+
from zrb.runner.web_config import User
|
5
|
+
from zrb.runner.web_util import get_html_auth_link
|
6
|
+
from zrb.util.file import read_file
|
7
|
+
from zrb.util.string.format import fstring_format
|
8
|
+
|
9
|
+
|
10
|
+
def show_error_page(user: User, root_group: AnyGroup, status_code: int, message: str):
|
11
|
+
from fastapi.responses import HTMLResponse
|
12
|
+
|
13
|
+
_DIR = os.path.dirname(__file__)
|
14
|
+
_VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
|
15
|
+
auth_link = get_html_auth_link(user)
|
16
|
+
return HTMLResponse(
|
17
|
+
fstring_format(
|
18
|
+
_VIEW_TEMPLATE,
|
19
|
+
{
|
20
|
+
"name": root_group.name,
|
21
|
+
"description": root_group.description,
|
22
|
+
"auth_link": auth_link,
|
23
|
+
"error_status_code": status_code,
|
24
|
+
"error_message": message,
|
25
|
+
},
|
26
|
+
)
|
27
|
+
)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
+
<meta name="color-scheme" content="light dark">
|
7
|
+
<link rel="stylesheet" href="/static/pico.min.css">
|
8
|
+
<link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
|
9
|
+
<title>Zrb</title>
|
10
|
+
<link rel="stylesheet" href="/static/common.css">
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<header class="container">
|
14
|
+
<hgroup>
|
15
|
+
<h1>{name}</h1>
|
16
|
+
<p>{description}</p>
|
17
|
+
<nav>
|
18
|
+
<ul>
|
19
|
+
<li><a href="/">🏠 Home</a></li>
|
20
|
+
<li><a href="/docs">💻 API Documentation</a></li>
|
21
|
+
</ul>
|
22
|
+
<ul>
|
23
|
+
<li>{auth_link}</li>
|
24
|
+
</ul>
|
25
|
+
</nav>
|
26
|
+
</hgroup>
|
27
|
+
</header>
|
28
|
+
<main class="container">
|
29
|
+
<h2>{error_status_code}</h2>
|
30
|
+
<p>{error_message}</p>
|
31
|
+
</main>
|
32
|
+
</body>
|
33
|
+
</html>
|