zrb 1.0.0a20__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 (133) 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/config.py +2 -2
  61. zrb/content_transformer/any_content_transformer.py +7 -0
  62. zrb/content_transformer/content_transformer.py +6 -0
  63. zrb/runner/cli.py +4 -6
  64. zrb/runner/web_app.py +28 -238
  65. zrb/runner/web_config/config.py +91 -0
  66. zrb/runner/web_config/config_factory.py +26 -0
  67. zrb/runner/web_route/docs_route.py +17 -0
  68. zrb/runner/web_route/error_page/serve_default_404.py +28 -0
  69. zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +2 -2
  70. zrb/runner/{web_controller → web_route}/error_page/view.html +6 -0
  71. zrb/runner/web_route/home_page/home_page_route.py +51 -0
  72. zrb/runner/{web_controller → web_route}/home_page/view.html +1 -0
  73. zrb/runner/web_route/login_api_route.py +31 -0
  74. zrb/runner/web_route/login_page/login_page_route.py +39 -0
  75. zrb/runner/{web_controller → web_route}/login_page/view.html +1 -0
  76. zrb/runner/web_route/logout_api_route.py +18 -0
  77. zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
  78. zrb/runner/{web_controller → web_route}/logout_page/view.html +1 -0
  79. zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
  80. zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +1 -0
  81. zrb/runner/web_route/node_page/node_page_route.py +50 -0
  82. zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +5 -5
  83. zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +1 -0
  84. zrb/runner/web_route/refresh_token_api_route.py +38 -0
  85. zrb/runner/web_route/static/refresh-token.template.js +22 -0
  86. zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +1 -1
  87. zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +5 -6
  88. zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +9 -3
  89. zrb/runner/web_route/static/static_route.py +44 -0
  90. zrb/runner/web_route/task_input_api_route.py +47 -0
  91. zrb/runner/web_route/task_session_api_route.py +102 -0
  92. zrb/runner/web_schema/session.py +5 -0
  93. zrb/runner/web_schema/token.py +11 -0
  94. zrb/runner/web_schema/user.py +32 -0
  95. zrb/runner/web_util/cookie.py +29 -0
  96. zrb/runner/{web_util.py → web_util/html.py} +1 -18
  97. zrb/runner/web_util/token.py +68 -0
  98. zrb/runner/web_util/user.py +63 -0
  99. zrb/session/session.py +6 -4
  100. zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
  101. zrb/task/base_task.py +29 -4
  102. zrb/task/base_trigger.py +2 -0
  103. zrb/task/cmd_task.py +2 -0
  104. zrb/task/http_check.py +2 -0
  105. zrb/task/llm_task.py +2 -0
  106. zrb/task/make_task.py +2 -0
  107. zrb/task/rsync_task.py +2 -0
  108. zrb/task/scaffolder.py +8 -5
  109. zrb/task/scheduler.py +2 -0
  110. zrb/task/tcp_check.py +2 -0
  111. zrb/task_status/task_status.py +4 -3
  112. {zrb-1.0.0a20.dist-info → zrb-1.0.0b1.dist-info}/METADATA +8 -52
  113. {zrb-1.0.0a20.dist-info → zrb-1.0.0b1.dist-info}/RECORD +126 -81
  114. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
  115. zrb/runner/web_config.py +0 -288
  116. zrb/runner/web_controller/home_page/__init__.py +0 -0
  117. zrb/runner/web_controller/home_page/controller.py +0 -33
  118. zrb/runner/web_controller/login_page/controller.py +0 -25
  119. zrb/runner/web_controller/logout_page/controller.py +0 -26
  120. zrb/runner/web_controller/session_page/__init__.py +0 -0
  121. /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
  122. /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
  123. /zrb/runner/{web_controller → web_route}/__init__.py +0 -0
  124. /zrb/runner/{web_controller/group_info_page → web_route/home_page}/__init__.py +0 -0
  125. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
  126. /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
  127. /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
  128. /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
  129. /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
  130. /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
  131. /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
  132. {zrb-1.0.0a20.dist-info → zrb-1.0.0b1.dist-info}/WHEEL +0 -0
  133. {zrb-1.0.0a20.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,8 +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>
37
+ <script src="/refresh-token.js"></script>
38
+
33
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
+ )
@@ -29,4 +29,5 @@
29
29
  {task_info}
30
30
  </main>
31
31
  </body>
32
+ <script src="/refresh-token.js"></script>
32
33
  </html>
@@ -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
+ )
@@ -45,6 +45,7 @@
45
45
  </article>
46
46
  </main>
47
47
  <script src="/static/login/event.js"></script>
48
+ <script src="/refresh-token.js"></script>
48
49
  </body>
49
50
 
50
51
  </html>
@@ -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
+ )
@@ -35,6 +35,7 @@
35
35
  </article>
36
36
  </main>
37
37
  <script src="/static/logout/event.js"></script>
38
+ <script src="/refresh-token.js"></script>
38
39
  </body>
39
40
 
40
41
  </html>
@@ -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__)
@@ -33,4 +33,5 @@
33
33
  {task_info}
34
34
  </main>
35
35
  </body>
36
+ <script src="/refresh-token.js"></script>
36
37
  </html>
@@ -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,
@@ -33,11 +33,11 @@ def show_session_page(
33
33
  parent_url = "/".join(parent_url_parts)
34
34
  # Assemble session api url
35
35
  session_url_parts = list(url_parts)
36
- session_url_parts[1] = "api/sessions"
36
+ session_url_parts[1] = "api/v1/task-sessions"
37
37
  session_api_url = "/".join(session_url_parts)
38
38
  # Assemble input api url
39
39
  input_url_parts = list(url_parts)
40
- input_url_parts[1] = "api/inputs"
40
+ input_url_parts[1] = "api/v1/task-inputs"
41
41
  input_api_url = "/".join(input_url_parts)
42
42
  # Assemble ui url
43
43
  ui_url_parts = list(url_parts)
@@ -88,4 +88,5 @@
88
88
  <script src="/static/session/past-session.js"></script>
89
89
  <script src="/static/session/current-session.js"></script>
90
90
  <script src="/static/session/event.js"></script>
91
+ <script src="/refresh-token.js"></script>
91
92
  </html>
@@ -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,22 @@
1
+ function refreshAuthToken(){
2
+ const refreshUrl = "/api/v1/refresh-token";
3
+ async function refresh() {
4
+ try {
5
+ const response = await fetch(refreshUrl, {
6
+ method: "POST",
7
+ headers: { "Content-Type": "application/json" },
8
+ credentials: "include", // Include cookies in the request
9
+ });
10
+
11
+ if (!response.ok) {
12
+ throw new Error(`HTTP error! Status: ${response.status}`);
13
+ }
14
+ console.log("Token refreshed successfully");
15
+ } catch (error) {
16
+ console.error("Cannot refresh token", error)
17
+ }
18
+ }
19
+ setInterval(refresh, refreshIntervalSeconds * 1000);
20
+ refresh();
21
+ }
22
+ refreshAuthToken();
@@ -1,5 +1,5 @@
1
1
  const CURRENT_SESSION = {
2
- async pollCurrentSession() {
2
+ async startPolling() {
3
3
  const resultTextarea = document.getElementById("result-textarea");
4
4
  const logTextarea = document.getElementById("log-textarea");
5
5
  const submitTaskForm = document.getElementById("submit-task-form");
@@ -1,7 +1,7 @@
1
1
  window.addEventListener("load", async function () {
2
2
  // Get current session
3
3
  if (cfg.SESSION_NAME != "") {
4
- CURRENT_SESSION.pollCurrentSession();
4
+ CURRENT_SESSION.startPolling();
5
5
  }
6
6
  // set maxStartDate to today
7
7
  const tomorrow = new Date();
@@ -16,8 +16,6 @@ window.addEventListener("load", async function () {
16
16
  const formattedToday = UTIL.toLocalDateInputValue(today);
17
17
  const minStartAtInput = document.getElementById("min-start-at-input");
18
18
  minStartAtInput.value = formattedToday;
19
- // Update session
20
- PAST_SESSION.pollPastSession();
21
19
  });
22
20
 
23
21
 
@@ -63,11 +61,12 @@ submitTaskForm.addEventListener("input", async function(event) {
63
61
  } catch (error) {
64
62
  console.error("Error during fetch:", error);
65
63
  }
66
- })
64
+ });
67
65
 
68
66
 
69
67
  function openPastSessionDialog(event) {
70
68
  event.preventDefault();
69
+ PAST_SESSION.startPolling();
71
70
  const dialog = document.getElementById("past-session-dialog")
72
71
  dialog.showModal();
73
72
  }
@@ -75,6 +74,7 @@ function openPastSessionDialog(event) {
75
74
 
76
75
  function closePastSessionDialog(event) {
77
76
  event.preventDefault();
77
+ PAST_SESSION.stopPolling();
78
78
  const dialog = document.getElementById("past-session-dialog")
79
79
  dialog.close();
80
80
  }
@@ -109,8 +109,7 @@ async function submitNewSessionForm(event) {
109
109
  const data = await response.json();
110
110
  cfg.SESSION_NAME = data.session_name;
111
111
  history.pushState(null, "", `${cfg.CURRENT_URL}${cfg.SESSION_NAME}`);
112
- await PAST_SESSION.getAndRenderPastSession(0);
113
- await CURRENT_SESSION.pollCurrentSession();
112
+ await CURRENT_SESSION.startPolling();
114
113
  } else {
115
114
  console.error("Error:", response);
116
115
  }