zrb 1.0.0a21__py3-none-any.whl → 1.0.0b1__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.
Files changed (132) hide show
  1. zrb/__init__.py +2 -1
  2. zrb/builtin/llm/llm_chat.py +2 -2
  3. zrb/builtin/llm/tool/web.py +1 -1
  4. zrb/builtin/project/add/fastapp/fastapp_task.py +2 -0
  5. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +4 -1
  6. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +16 -1
  7. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +91 -9
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/{my_entity_usecase.py → my_entity_service.py} +7 -13
  9. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service_factory.py +8 -0
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +37 -0
  11. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +1 -1
  12. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +13 -0
  13. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +22 -0
  14. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -0
  15. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +7 -0
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +1 -1
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +1 -2
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +3 -3
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +10 -10
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +4 -4
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app.py +42 -5
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/{base_usecase.py → base_service.py} +3 -3
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/view.py +37 -0
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +24 -0
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +2 -2
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +2 -2
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +2 -2
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/{user_usecase.py → user_service.py} +7 -7
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +6 -0
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +42 -13
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +74 -0
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +6 -0
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/homepage.html +6 -0
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-192x192.png +0 -0
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-512x512.png +0 -0
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/favicon-32x32.png +0 -0
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.amber.min.css +4 -0
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.blue.min.css +4 -0
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.cyan.min.css +4 -0
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.fuchsia.min.css +4 -0
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.green.min.css +4 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.grey.min.css +4 -0
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.indigo.min.css +4 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.jade.min.css +4 -0
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.lime.min.css +4 -0
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.min.css +4 -0
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.orange.min.css +4 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pink.min.css +4 -0
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pumpkin.min.css +4 -0
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.purple.min.css +4 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.red.min.css +4 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.sand.min.css +4 -0
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.slate.min.css +4 -0
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.violet.min.css +4 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.yellow.min.css +4 -0
  56. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.zinc.min.css +4 -0
  57. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +34 -0
  58. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -0
  59. zrb/builtin/python.py +1 -1
  60. zrb/content_transformer/any_content_transformer.py +7 -0
  61. zrb/content_transformer/content_transformer.py +6 -0
  62. zrb/runner/cli.py +4 -6
  63. zrb/runner/web_app.py +28 -280
  64. zrb/runner/web_config/config.py +91 -0
  65. zrb/runner/web_config/config_factory.py +26 -0
  66. zrb/runner/web_route/docs_route.py +17 -0
  67. zrb/runner/web_route/error_page/serve_default_404.py +28 -0
  68. zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +2 -2
  69. zrb/runner/{web_controller → web_route}/error_page/view.html +5 -0
  70. zrb/runner/web_route/home_page/home_page_route.py +51 -0
  71. zrb/runner/web_route/login_api_route.py +31 -0
  72. zrb/runner/web_route/login_page/login_page_route.py +39 -0
  73. zrb/runner/web_route/logout_api_route.py +18 -0
  74. zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
  75. zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
  76. zrb/runner/web_route/node_page/node_page_route.py +50 -0
  77. zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +3 -3
  78. zrb/runner/web_route/refresh_token_api_route.py +38 -0
  79. zrb/runner/web_route/static/static_route.py +44 -0
  80. zrb/runner/web_route/task_input_api_route.py +47 -0
  81. zrb/runner/web_route/task_session_api_route.py +102 -0
  82. zrb/runner/web_schema/session.py +5 -0
  83. zrb/runner/web_schema/token.py +11 -0
  84. zrb/runner/web_schema/user.py +32 -0
  85. zrb/runner/web_util/cookie.py +29 -0
  86. zrb/runner/{web_util.py → web_util/html.py} +1 -23
  87. zrb/runner/web_util/token.py +68 -0
  88. zrb/runner/web_util/user.py +63 -0
  89. zrb/session/session.py +6 -4
  90. zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
  91. zrb/task/base_task.py +29 -4
  92. zrb/task/base_trigger.py +2 -0
  93. zrb/task/cmd_task.py +2 -0
  94. zrb/task/http_check.py +2 -0
  95. zrb/task/llm_task.py +2 -0
  96. zrb/task/make_task.py +2 -0
  97. zrb/task/rsync_task.py +2 -0
  98. zrb/task/scaffolder.py +8 -5
  99. zrb/task/scheduler.py +2 -0
  100. zrb/task/tcp_check.py +2 -0
  101. zrb/task_status/task_status.py +4 -3
  102. {zrb-1.0.0a21.dist-info → zrb-1.0.0b1.dist-info}/METADATA +1 -1
  103. {zrb-1.0.0a21.dist-info → zrb-1.0.0b1.dist-info}/RECORD +125 -81
  104. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
  105. zrb/runner/web_config.py +0 -274
  106. zrb/runner/web_controller/home_page/__init__.py +0 -0
  107. zrb/runner/web_controller/home_page/controller.py +0 -33
  108. zrb/runner/web_controller/login_page/controller.py +0 -25
  109. zrb/runner/web_controller/logout_page/controller.py +0 -26
  110. zrb/runner/web_controller/session_page/__init__.py +0 -0
  111. /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
  112. /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/{factory.py → my_entity_repository_factory.py} +0 -0
  113. /zrb/runner/{web_controller → web_route}/__init__.py +0 -0
  114. /zrb/runner/{web_controller/group_info_page → web_route/home_page}/__init__.py +0 -0
  115. /zrb/runner/{web_controller → web_route}/home_page/view.html +0 -0
  116. /zrb/runner/{web_controller → web_route}/login_page/view.html +0 -0
  117. /zrb/runner/{web_controller → web_route}/logout_page/view.html +0 -0
  118. /zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +0 -0
  119. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
  120. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +0 -0
  121. /zrb/runner/{refresh-token.template.js → web_route/static/refresh-token.template.js} +0 -0
  122. /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
  123. /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
  124. /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
  125. /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
  126. /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
  127. /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
  128. /zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +0 -0
  129. /zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +0 -0
  130. /zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +0 -0
  131. {zrb-1.0.0a21.dist-info → zrb-1.0.0b1.dist-info}/WHEEL +0 -0
  132. {zrb-1.0.0a21.dist-info → zrb-1.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,91 @@
1
+ from typing import Callable
2
+
3
+ from zrb.runner.web_schema.user import User
4
+ from zrb.task.any_task import AnyTask
5
+
6
+
7
+ class WebConfig:
8
+ def __init__(
9
+ self,
10
+ port: int,
11
+ secret_key: str,
12
+ access_token_expire_minutes: int,
13
+ refresh_token_expire_minutes: int,
14
+ access_token_cookie_name: str,
15
+ refresh_token_cookie_name: str,
16
+ enable_auth: bool,
17
+ super_admin_username: str,
18
+ super_admin_password: str,
19
+ guest_username: str,
20
+ guest_accessible_tasks: list[AnyTask | str] = [],
21
+ find_user_by_username: Callable[[str], User | None] | None = None,
22
+ ):
23
+ self.secret_key = secret_key
24
+ self.access_token_expire_minutes = access_token_expire_minutes
25
+ self.refresh_token_expire_minutes = refresh_token_expire_minutes
26
+ self.access_token_cookie_name = access_token_cookie_name
27
+ self.refresh_token_cookie_name = refresh_token_cookie_name
28
+ self.enable_auth = enable_auth
29
+ self.port = port
30
+ self._user_list = []
31
+ self.super_admin_username = super_admin_username
32
+ self.super_admin_password = super_admin_password
33
+ self.guest_username = guest_username
34
+ self.guest_accessible_tasks = guest_accessible_tasks
35
+ self._find_user_by_username = find_user_by_username
36
+
37
+ @property
38
+ def default_user(self) -> User:
39
+ if self.enable_auth:
40
+ return User(
41
+ username=self.guest_username,
42
+ password="",
43
+ is_guest=True,
44
+ accessible_tasks=self.guest_accessible_tasks,
45
+ )
46
+ return User(
47
+ username=self.guest_username,
48
+ password="",
49
+ is_guest=True,
50
+ is_super_admin=True,
51
+ )
52
+
53
+ @property
54
+ def super_admin(self) -> User:
55
+ return User(
56
+ username=self.super_admin_username,
57
+ password=self.super_admin_password,
58
+ is_super_admin=True,
59
+ )
60
+
61
+ @property
62
+ def user_list(self) -> list[User]:
63
+ if not self.enable_auth:
64
+ return [self.default_user]
65
+ return self._user_list + [self.super_admin, self.default_user]
66
+
67
+ def set_guest_accessible_tasks(self, tasks: list[AnyTask | str]):
68
+ self.guest_accessible_tasks = tasks
69
+
70
+ def set_find_user_by_username(
71
+ self, find_user_by_username: Callable[[str], User | None]
72
+ ):
73
+ self._find_user_by_username = find_user_by_username
74
+
75
+ def append_user(self, user: User):
76
+ duplicates = [
77
+ existing_user
78
+ for existing_user in self.user_list
79
+ if existing_user.username == user.username
80
+ ]
81
+ if len(duplicates) > 0:
82
+ raise ValueError(f"User already exists {user.username}")
83
+ self._user_list.append(user)
84
+
85
+ def find_user_by_username(self, username: str) -> User | None:
86
+ user = None
87
+ if self._find_user_by_username is not None:
88
+ user = self._find_user_by_username(username)
89
+ if user is None:
90
+ user = next((u for u in self.user_list if u.username == username), None)
91
+ return user
@@ -0,0 +1,26 @@
1
+ from zrb.config import (
2
+ WEB_ACCESS_TOKEN_COOKIE_NAME,
3
+ WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
4
+ WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
5
+ WEB_ENABLE_AUTH,
6
+ WEB_GUEST_USERNAME,
7
+ WEB_HTTP_PORT,
8
+ WEB_REFRESH_TOKEN_COOKIE_NAME,
9
+ WEB_SECRET_KEY,
10
+ WEB_SUPER_ADMIN_PASSWORD,
11
+ WEB_SUPER_ADMIN_USERNAME,
12
+ )
13
+ from zrb.runner.web_config.config import WebConfig
14
+
15
+ web_config = WebConfig(
16
+ port=WEB_HTTP_PORT,
17
+ secret_key=WEB_SECRET_KEY,
18
+ access_token_expire_minutes=WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
19
+ refresh_token_expire_minutes=WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
20
+ access_token_cookie_name=WEB_ACCESS_TOKEN_COOKIE_NAME,
21
+ refresh_token_cookie_name=WEB_REFRESH_TOKEN_COOKIE_NAME,
22
+ enable_auth=WEB_ENABLE_AUTH,
23
+ super_admin_username=WEB_SUPER_ADMIN_USERNAME,
24
+ super_admin_password=WEB_SUPER_ADMIN_PASSWORD,
25
+ guest_username=WEB_GUEST_USERNAME,
26
+ )
@@ -0,0 +1,17 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ # We want fastapi to only be loaded when necessary to decrease footprint
5
+ from fastapi import FastAPI
6
+
7
+
8
+ def serve_docs(app: "FastAPI") -> None:
9
+ from fastapi.openapi.docs import get_swagger_ui_html
10
+
11
+ @app.get("/docs", include_in_schema=False)
12
+ async def swagger_ui_html():
13
+ return get_swagger_ui_html(
14
+ openapi_url="/openapi.json",
15
+ title="Zrb",
16
+ swagger_favicon_url="/static/favicon-32x32.png",
17
+ )
@@ -0,0 +1,28 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from zrb.group.any_group import AnyGroup
4
+ from zrb.runner.web_config.config import WebConfig
5
+ from zrb.runner.web_route.error_page.show_error_page import show_error_page
6
+ from zrb.runner.web_util.user import get_user_from_request
7
+
8
+ if TYPE_CHECKING:
9
+ # We want fastapi to only be loaded when necessary to decrease footprint
10
+ from fastapi import FastAPI
11
+
12
+
13
+ def serve_default_404(
14
+ app: "FastAPI",
15
+ root_group: AnyGroup,
16
+ web_config: WebConfig,
17
+ ) -> None:
18
+ from fastapi import Request
19
+ from fastapi.exception_handlers import http_exception_handler
20
+ from fastapi.responses import HTMLResponse
21
+
22
+ @app.exception_handler(404)
23
+ async def default_404(request: Request, exc: Exception) -> HTMLResponse:
24
+ if request.url.path.startswith("/api"):
25
+ # Re-raise the exception to let FastAPI handle it
26
+ return await http_exception_handler(request, exc)
27
+ user = await get_user_from_request(web_config, request)
28
+ return show_error_page(user, root_group, 404, "Not found")
@@ -1,8 +1,8 @@
1
1
  import os
2
2
 
3
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
4
+ from zrb.runner.web_schema.user import User
5
+ from zrb.runner.web_util.html import get_html_auth_link
6
6
  from zrb.util.file import read_file
7
7
  from zrb.util.string.format import fstring_format
8
8
 
@@ -1,5 +1,6 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
+
3
4
  <head>
4
5
  <meta charset="utf-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -9,6 +10,7 @@
9
10
  <title>Zrb</title>
10
11
  <link rel="stylesheet" href="/static/common.css">
11
12
  </head>
13
+
12
14
  <body>
13
15
  <header class="container">
14
16
  <hgroup>
@@ -26,9 +28,12 @@
26
28
  </hgroup>
27
29
  </header>
28
30
  <main class="container">
31
+ <article>
29
32
  <h2>{error_status_code}</h2>
30
33
  <p>{error_message}</p>
34
+ </article>
31
35
  </main>
32
36
  </body>
33
37
  <script src="/refresh-token.js"></script>
38
+
34
39
  </html>
@@ -0,0 +1,51 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.web_config.config import WebConfig
6
+ from zrb.runner.web_util.html import (
7
+ get_html_auth_link,
8
+ get_html_subgroup_info,
9
+ get_html_subtask_info,
10
+ )
11
+ from zrb.runner.web_util.user import get_user_from_request
12
+ from zrb.util.file import read_file
13
+ from zrb.util.string.format import fstring_format
14
+
15
+ if TYPE_CHECKING:
16
+ # We want fastapi to only be loaded when necessary to decrease footprint
17
+ from fastapi import FastAPI
18
+
19
+
20
+ def serve_home_page(
21
+ app: "FastAPI",
22
+ root_group: AnyGroup,
23
+ web_config: WebConfig,
24
+ ) -> None:
25
+ from fastapi import Request
26
+ from fastapi.responses import HTMLResponse
27
+
28
+ # Serve homepage
29
+ @app.get("/", response_class=HTMLResponse, include_in_schema=False)
30
+ @app.get("/ui", response_class=HTMLResponse, include_in_schema=False)
31
+ @app.get("/ui/", response_class=HTMLResponse, include_in_schema=False)
32
+ async def home_page_ui(request: Request) -> HTMLResponse:
33
+
34
+ _DIR = os.path.dirname(__file__)
35
+ _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
36
+ user = await get_user_from_request(web_config, request)
37
+ group_info = get_html_subgroup_info(user, "/ui/", root_group)
38
+ task_info = get_html_subtask_info(user, "/ui/", root_group)
39
+ auth_link = get_html_auth_link(user)
40
+ return HTMLResponse(
41
+ fstring_format(
42
+ _VIEW_TEMPLATE,
43
+ {
44
+ "group_info": group_info,
45
+ "task_info": task_info,
46
+ "name": root_group.name,
47
+ "description": root_group.description,
48
+ "auth_link": auth_link,
49
+ },
50
+ )
51
+ )
@@ -0,0 +1,31 @@
1
+ from typing import TYPE_CHECKING, Annotated
2
+
3
+ from zrb.runner.web_config.config import WebConfig
4
+ from zrb.runner.web_util.cookie import set_auth_cookie
5
+ from zrb.runner.web_util.token import generate_tokens_by_credentials
6
+
7
+ if TYPE_CHECKING:
8
+ # We want fastapi to only be loaded when necessary to decrease footprint
9
+ from fastapi import FastAPI
10
+
11
+
12
+ def serve_login_api(app: "FastAPI", web_config: WebConfig) -> None:
13
+ from fastapi import Depends, Response
14
+ from fastapi.responses import JSONResponse
15
+ from fastapi.security import OAuth2PasswordRequestForm
16
+
17
+ @app.post("/api/v1/login")
18
+ async def login_api(
19
+ response: Response, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
20
+ ):
21
+ token = generate_tokens_by_credentials(
22
+ web_config=web_config,
23
+ username=form_data.username,
24
+ password=form_data.password,
25
+ )
26
+ if token is None:
27
+ return JSONResponse(
28
+ content={"detail": "Incorrect username or password"}, status_code=400
29
+ )
30
+ set_auth_cookie(web_config, response, token)
31
+ return token
@@ -0,0 +1,39 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.web_config.config import WebConfig
6
+ from zrb.runner.web_util.html import get_html_auth_link
7
+ from zrb.runner.web_util.user import get_user_from_request
8
+ from zrb.util.file import read_file
9
+ from zrb.util.string.format import fstring_format
10
+
11
+ if TYPE_CHECKING:
12
+ # We want fastapi to only be loaded when necessary to decrease footprint
13
+ from fastapi import FastAPI
14
+
15
+
16
+ def serve_login_page(
17
+ app: "FastAPI",
18
+ root_group: AnyGroup,
19
+ web_config: WebConfig,
20
+ ) -> None:
21
+ from fastapi import Request
22
+ from fastapi.responses import HTMLResponse
23
+
24
+ @app.get("/login", response_class=HTMLResponse, include_in_schema=False)
25
+ async def login(request: Request) -> HTMLResponse:
26
+ _DIR = os.path.dirname(__file__)
27
+ _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
28
+ user = await get_user_from_request(web_config, request)
29
+ auth_link = get_html_auth_link(user)
30
+ return HTMLResponse(
31
+ fstring_format(
32
+ _VIEW_TEMPLATE,
33
+ {
34
+ "name": root_group.name,
35
+ "description": root_group.description,
36
+ "auth_link": auth_link,
37
+ },
38
+ )
39
+ )
@@ -0,0 +1,18 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from zrb.runner.web_config.config import WebConfig
4
+
5
+ if TYPE_CHECKING:
6
+ # We want fastapi to only be loaded when necessary to decrease footprint
7
+ from fastapi import FastAPI
8
+
9
+
10
+ def serve_logout_api(app: "FastAPI", web_config: WebConfig) -> None:
11
+ from fastapi import Response
12
+
13
+ @app.get("/api/v1/logout")
14
+ @app.post("/api/v1/logout")
15
+ async def logout_api(response: Response):
16
+ response.delete_cookie(web_config.access_token_cookie_name)
17
+ response.delete_cookie(web_config.refresh_token_cookie_name)
18
+ return {"message": "Logout successful"}
@@ -0,0 +1,40 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.web_config.config import WebConfig
6
+ from zrb.runner.web_util.html import get_html_auth_link
7
+ from zrb.runner.web_util.user import get_user_from_request
8
+ from zrb.util.file import read_file
9
+ from zrb.util.string.format import fstring_format
10
+
11
+ if TYPE_CHECKING:
12
+ # We want fastapi to only be loaded when necessary to decrease footprint
13
+ from fastapi import FastAPI
14
+
15
+
16
+ def serve_logout_page(
17
+ app: "FastAPI",
18
+ root_group: AnyGroup,
19
+ web_config: WebConfig,
20
+ ) -> None:
21
+ from fastapi import Request
22
+ from fastapi.responses import HTMLResponse
23
+
24
+ @app.get("/logout", response_class=HTMLResponse, include_in_schema=False)
25
+ async def logout(request: Request) -> HTMLResponse:
26
+ _DIR = os.path.dirname(__file__)
27
+ _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
28
+ user = await get_user_from_request(web_config, request)
29
+ auth_link = get_html_auth_link(user)
30
+ return HTMLResponse(
31
+ fstring_format(
32
+ _VIEW_TEMPLATE,
33
+ {
34
+ "name": root_group.name,
35
+ "description": root_group.description,
36
+ "auth_link": auth_link,
37
+ "user": user,
38
+ },
39
+ )
40
+ )
@@ -1,8 +1,8 @@
1
1
  import os
2
2
 
3
3
  from zrb.group.any_group import AnyGroup
4
- from zrb.runner.web_config import User
5
- from zrb.runner.web_util import (
4
+ from zrb.runner.web_schema.user import User
5
+ from zrb.runner.web_util.html import (
6
6
  get_html_auth_link,
7
7
  get_html_subgroup_info,
8
8
  get_html_subtask_info,
@@ -11,7 +11,7 @@ from zrb.util.file import read_file
11
11
  from zrb.util.string.format import fstring_format
12
12
 
13
13
 
14
- def show_group_info_page(user: User, root_group: AnyGroup, group: AnyGroup, url: str):
14
+ def show_group_page(user: User, root_group: AnyGroup, group: AnyGroup, url: str):
15
15
  from fastapi.responses import HTMLResponse
16
16
 
17
17
  _DIR = os.path.dirname(__file__)
@@ -0,0 +1,50 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.context.shared_context import SharedContext
5
+ from zrb.group.any_group import AnyGroup
6
+ from zrb.runner.web_config.config import WebConfig
7
+ from zrb.runner.web_route.error_page.show_error_page import show_error_page
8
+ from zrb.runner.web_route.node_page.group.show_group_page import show_group_page
9
+ from zrb.runner.web_route.node_page.task.show_task_page import show_task_page
10
+ from zrb.runner.web_util.user import get_user_from_request
11
+ from zrb.session.session import Session
12
+ from zrb.task.any_task import AnyTask
13
+ from zrb.util.group import NodeNotFoundError, extract_node_from_args
14
+
15
+ if TYPE_CHECKING:
16
+ # We want fastapi to only be loaded when necessary to decrease footprint
17
+ from fastapi import FastAPI
18
+
19
+
20
+ def serve_node_page(
21
+ app: "FastAPI",
22
+ root_group: AnyGroup,
23
+ web_config: WebConfig,
24
+ ) -> None:
25
+ from fastapi import Request
26
+ from fastapi.responses import HTMLResponse
27
+
28
+ @app.get("/ui/{path:path}", response_class=HTMLResponse, include_in_schema=False)
29
+ async def ui_page(path: str, request: Request) -> HTMLResponse:
30
+ user = await get_user_from_request(web_config, request)
31
+ # Avoid capturing '/ui' itself
32
+ if not path:
33
+ return show_error_page(user, root_group, 422, "Undefined path")
34
+ args = path.strip("/").split("/")
35
+ try:
36
+ node, node_path, residual_args = extract_node_from_args(root_group, args)
37
+ except NodeNotFoundError as e:
38
+ return show_error_page(user, root_group, 404, str(e))
39
+ url = f"/ui/{'/'.join(node_path)}/"
40
+ if isinstance(node, AnyTask):
41
+ if not user.can_access_task(node):
42
+ return show_error_page(user, root_group, 403, "Forbidden")
43
+ shared_ctx = SharedContext(env=dict(os.environ))
44
+ session = Session(shared_ctx=shared_ctx, root_group=root_group)
45
+ return show_task_page(user, root_group, node, session, url, residual_args)
46
+ elif isinstance(node, AnyGroup):
47
+ if not user.can_access_group(node):
48
+ return show_error_page(user, root_group, 403, "Forbidden")
49
+ return show_group_page(user, root_group, node, url)
50
+ return show_error_page(user, root_group, 404, "Not found")
@@ -2,15 +2,15 @@ import json
2
2
  import os
3
3
 
4
4
  from zrb.group.any_group import AnyGroup
5
- from zrb.runner.web_config import User
6
- from zrb.runner.web_util import get_html_auth_link
5
+ from zrb.runner.web_schema.user import User
6
+ from zrb.runner.web_util.html import get_html_auth_link
7
7
  from zrb.session.any_session import AnySession
8
8
  from zrb.task.any_task import AnyTask
9
9
  from zrb.util.file import read_file
10
10
  from zrb.util.string.format import fstring_format
11
11
 
12
12
 
13
- def show_session_page(
13
+ def show_task_page(
14
14
  user: User,
15
15
  root_group: AnyGroup,
16
16
  task: AnyTask,
@@ -0,0 +1,38 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from zrb.runner.web_config.config import WebConfig
4
+ from zrb.runner.web_schema.token import RefreshTokenRequest
5
+ from zrb.runner.web_util.cookie import set_auth_cookie
6
+ from zrb.runner.web_util.token import regenerate_tokens
7
+
8
+ if TYPE_CHECKING:
9
+ # We want fastapi to only be loaded when necessary to decrease footprint
10
+ from fastapi import FastAPI
11
+
12
+
13
+ def serve_refresh_token_api(app: "FastAPI", web_config: WebConfig) -> None:
14
+ from fastapi import Cookie, Response
15
+ from fastapi.responses import JSONResponse
16
+
17
+ @app.post("/api/v1/refresh-token")
18
+ async def refresh_token_api(
19
+ response: Response,
20
+ body: RefreshTokenRequest = None,
21
+ refresh_token_cookie: str = Cookie(
22
+ None, alias=web_config.refresh_token_cookie_name
23
+ ),
24
+ ):
25
+ # Try to get the refresh token from the request body first
26
+ refresh_token = body.refresh_token if body else None
27
+ # If not in the body, try to get it from the cookie
28
+ if not refresh_token:
29
+ refresh_token = refresh_token_cookie
30
+ # If we still don't have a refresh token, raise an exception
31
+ if not refresh_token:
32
+ return JSONResponse(
33
+ content={"detail": "Refresh token not provided"}, status_code=400
34
+ )
35
+ # Get token
36
+ new_token = regenerate_tokens(web_config, refresh_token)
37
+ set_auth_cookie(web_config, response, new_token)
38
+ return new_token
@@ -0,0 +1,44 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.runner.web_config.config import WebConfig
5
+ from zrb.util.file import read_file
6
+
7
+ if TYPE_CHECKING:
8
+ # We want fastapi to only be loaded when necessary to decrease footprint
9
+ from fastapi import FastAPI
10
+
11
+
12
+ def serve_static_resources(app: "FastAPI", web_config: WebConfig) -> None:
13
+ from fastapi import HTTPException
14
+ from fastapi.responses import FileResponse, PlainTextResponse
15
+ from fastapi.staticfiles import StaticFiles
16
+
17
+ _STATIC_DIR = os.path.join(os.path.dirname(__file__), "resources")
18
+
19
+ app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
20
+
21
+ # Serve static files
22
+ @app.get("/static/{file_path:path}", include_in_schema=False)
23
+ async def static_files(file_path: str):
24
+ full_path = os.path.join(_STATIC_DIR, file_path)
25
+ if os.path.isfile(full_path):
26
+ return FileResponse(full_path)
27
+ raise HTTPException(status_code=404, detail="File not found")
28
+
29
+ @app.get("/refresh-token.js", include_in_schema=False)
30
+ async def refresh_token_js():
31
+ return PlainTextResponse(
32
+ content=_get_refresh_token_js(
33
+ 60 * web_config.refresh_token_expire_minutes / 3
34
+ ),
35
+ media_type="application/javascript",
36
+ )
37
+
38
+
39
+ def _get_refresh_token_js(refresh_interval_seconds: int):
40
+ _DIR = os.path.dirname(__file__)
41
+ return read_file(
42
+ os.path.join(_DIR, "refresh-token.template.js"),
43
+ {"refreshIntervalSeconds": f"{refresh_interval_seconds}"},
44
+ )
@@ -0,0 +1,47 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.group.any_group import AnyGroup
5
+ from zrb.runner.common_util import get_run_kwargs
6
+ from zrb.runner.web_config.config import WebConfig
7
+ from zrb.runner.web_util.user import get_user_from_request
8
+ from zrb.task.any_task import AnyTask
9
+ from zrb.util.group import NodeNotFoundError, extract_node_from_args
10
+
11
+ if TYPE_CHECKING:
12
+ # We want fastapi to only be loaded when necessary to decrease footprint
13
+ from fastapi import FastAPI
14
+
15
+
16
+ def serve_task_input_api(
17
+ app: "FastAPI",
18
+ root_group: AnyGroup,
19
+ web_config: WebConfig,
20
+ ) -> None:
21
+ from fastapi import Query, Request
22
+ from fastapi.responses import JSONResponse
23
+
24
+ @app.get("/api/v1/task-inputs/{path:path}", response_model=dict[str, str])
25
+ async def get_default_inputs_api(
26
+ path: str,
27
+ request: Request,
28
+ query: str = Query("{}", description="JSON encoded inputs"),
29
+ ) -> dict[str, str]:
30
+ """
31
+ Getting input completion for path
32
+ """
33
+ user = await get_user_from_request(web_config, request)
34
+ args = path.strip("/").split("/")
35
+ try:
36
+ task, _, _ = extract_node_from_args(root_group, args)
37
+ except NodeNotFoundError:
38
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)
39
+ if isinstance(task, AnyTask):
40
+ if not user.can_access_task(task):
41
+ return JSONResponse(content={"detail": "Forbidden"}, status_code=403)
42
+ query_dict = json.loads(query)
43
+ run_kwargs = get_run_kwargs(
44
+ task=task, args=[], kwargs=query_dict, prompt=False
45
+ )
46
+ return run_kwargs
47
+ return JSONResponse(content={"detail": "Not found"}, status_code=404)