zrb 1.5.16__py3-none-any.whl → 1.6.0__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 (46) hide show
  1. zrb/__init__.py +2 -2
  2. zrb/__main__.py +12 -12
  3. zrb/builtin/__init__.py +2 -2
  4. zrb/builtin/llm/chat_session.py +202 -0
  5. zrb/builtin/llm/history.py +6 -6
  6. zrb/builtin/llm/llm_ask.py +142 -0
  7. zrb/builtin/llm/tool/rag.py +39 -23
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +1 -1
  9. zrb/builtin/todo.py +21 -19
  10. zrb/callback/any_callback.py +1 -1
  11. zrb/callback/callback.py +69 -7
  12. zrb/config.py +261 -91
  13. zrb/context/shared_context.py +4 -2
  14. zrb/input/text_input.py +9 -6
  15. zrb/llm_config.py +65 -74
  16. zrb/runner/cli.py +13 -4
  17. zrb/runner/web_app.py +3 -3
  18. zrb/runner/web_config/config_factory.py +11 -22
  19. zrb/runner/web_route/error_page/show_error_page.py +16 -6
  20. zrb/runner/web_route/home_page/home_page_route.py +23 -7
  21. zrb/runner/web_route/home_page/view.html +19 -33
  22. zrb/runner/web_route/login_page/login_page_route.py +14 -4
  23. zrb/runner/web_route/login_page/view.html +33 -51
  24. zrb/runner/web_route/logout_page/logout_page_route.py +15 -5
  25. zrb/runner/web_route/logout_page/view.html +23 -41
  26. zrb/runner/web_route/node_page/group/show_group_page.py +26 -10
  27. zrb/runner/web_route/node_page/group/view.html +22 -37
  28. zrb/runner/web_route/node_page/task/show_task_page.py +34 -19
  29. zrb/runner/web_route/node_page/task/view.html +74 -88
  30. zrb/runner/web_route/static/global_template.html +27 -0
  31. zrb/runner/web_route/static/resources/common.css +21 -0
  32. zrb/runner/web_route/static/resources/common.js +28 -0
  33. zrb/runner/web_route/task_session_api_route.py +3 -1
  34. zrb/session_state_logger/session_state_logger_factory.py +2 -2
  35. zrb/task/base_task.py +4 -1
  36. zrb/task/base_trigger.py +47 -2
  37. zrb/task/cmd_task.py +3 -3
  38. zrb/task/llm/agent.py +10 -1
  39. zrb/task/llm/print_node.py +5 -6
  40. zrb/task/llm_task.py +1 -1
  41. zrb/util/git_subtree.py +1 -1
  42. {zrb-1.5.16.dist-info → zrb-1.6.0.dist-info}/METADATA +1 -1
  43. {zrb-1.5.16.dist-info → zrb-1.6.0.dist-info}/RECORD +45 -42
  44. zrb/builtin/llm/llm_chat.py +0 -124
  45. {zrb-1.5.16.dist-info → zrb-1.6.0.dist-info}/WHEEL +0 -0
  46. {zrb-1.5.16.dist-info → zrb-1.6.0.dist-info}/entry_points.txt +0 -0
zrb/llm_config.py CHANGED
@@ -1,4 +1,3 @@
1
- import os
2
1
  from typing import TYPE_CHECKING, Any
3
2
 
4
3
  if TYPE_CHECKING:
@@ -8,7 +7,7 @@ else:
8
7
  Model = Any
9
8
  Provider = Any
10
9
 
11
- from zrb.util.string.conversion import to_boolean
10
+ from zrb.config import CFG
12
11
 
13
12
  DEFAULT_SYSTEM_PROMPT = """
14
13
  You have access to tools.
@@ -65,72 +64,30 @@ class LLMConfig:
65
64
  default_enrich_context: bool | None = None,
66
65
  default_context_enrichment_threshold: int | None = None,
67
66
  ):
68
- self._default_model_name = (
69
- default_model_name
70
- if default_model_name is not None
71
- else os.getenv("ZRB_LLM_MODEL", None)
72
- )
73
- self._default_model_base_url = (
74
- default_base_url
75
- if default_base_url is not None
76
- else os.getenv("ZRB_LLM_BASE_URL", None)
77
- )
78
- self._default_model_api_key = (
79
- default_api_key
80
- if default_api_key is not None
81
- else os.getenv("ZRB_LLM_API_KEY", None)
82
- )
83
- self._default_system_prompt = (
84
- default_system_prompt
85
- if default_system_prompt is not None
86
- else os.getenv("ZRB_LLM_SYSTEM_PROMPT", None)
87
- )
88
- self._default_persona = (
89
- default_persona
90
- if default_persona is not None
91
- else os.getenv("ZRB_LLM_PERSONA", None)
92
- )
93
- self._default_special_instruction_prompt = (
94
- default_special_instruction_prompt
95
- if default_special_instruction_prompt is not None
96
- else os.getenv("ZRB_LLM_SPECIAL_INSTRUCTION_PROMPT", None)
97
- )
98
- self._default_summarization_prompt = (
99
- default_summarization_prompt
100
- if default_summarization_prompt is not None
101
- else os.getenv("ZRB_LLM_SUMMARIZATION_PROMPT", None)
102
- )
103
- self._default_context_enrichment_prompt = (
104
- default_context_enrichment_prompt
105
- if default_context_enrichment_prompt is not None
106
- else os.getenv("ZRB_LLM_CONTEXT_ENRICHMENT_PROMPT", None)
107
- )
108
- self._default_summarize_history = (
109
- default_summarize_history
110
- if default_summarize_history is not None
111
- else to_boolean(os.getenv("ZRB_LLM_SUMMARIZE_HISTORY", "true"))
112
- )
67
+ self._default_model_name = default_model_name
68
+ self._default_model_base_url = default_base_url
69
+ self._default_model_api_key = default_api_key
70
+ self._default_persona = default_persona
71
+ self._default_system_prompt = default_system_prompt
72
+ self._default_special_instruction_prompt = default_special_instruction_prompt
73
+ self._default_summarization_prompt = default_summarization_prompt
74
+ self._default_context_enrichment_prompt = default_context_enrichment_prompt
75
+ self._default_summarize_history = default_summarize_history
113
76
  self._default_history_summarization_threshold = (
114
77
  default_history_summarization_threshold
115
- if default_history_summarization_threshold is not None
116
- else int(os.getenv("ZRB_LLM_HISTORY_SUMMARIZATION_THRESHOLD", "5"))
117
- )
118
- self._default_enrich_context = (
119
- default_enrich_context
120
- if default_enrich_context is not None
121
- else to_boolean(os.getenv("ZRB_LLM_ENRICH_CONTEXT", "true"))
122
78
  )
79
+ self._default_enrich_context = default_enrich_context
123
80
  self._default_context_enrichment_threshold = (
124
81
  default_context_enrichment_threshold
125
- if default_context_enrichment_threshold is not None
126
- else int(os.getenv("ZRB_LLM_CONTEXT_ENRICHMENT_THRESHOLD", "5"))
127
82
  )
128
83
  self._default_provider = None
129
84
  self._default_model = None
130
85
 
131
86
  def _get_model_name(self) -> str | None:
132
87
  return (
133
- self._default_model_name if self._default_model_name is not None else None
88
+ self._default_model_name
89
+ if self._default_model_name is not None
90
+ else CFG.LLM_MODEL
134
91
  )
135
92
 
136
93
  def get_default_model_provider(self) -> Provider | str:
@@ -146,35 +103,53 @@ class LLMConfig:
146
103
 
147
104
  def get_default_system_prompt(self) -> str:
148
105
  return (
149
- DEFAULT_SYSTEM_PROMPT
150
- if self._default_system_prompt is None
151
- else self._default_system_prompt
106
+ self._default_system_prompt
107
+ if self._default_system_prompt is not None
108
+ else (
109
+ CFG.LLM_SYSTEM_PROMPT
110
+ if CFG.LLM_SYSTEM_PROMPT is not None
111
+ else DEFAULT_SYSTEM_PROMPT
112
+ )
152
113
  )
153
114
 
154
115
  def get_default_persona(self) -> str:
155
116
  return (
156
- DEFAULT_PERSONA if self._default_persona is None else self._default_persona
117
+ self._default_persona
118
+ if self._default_persona is not None
119
+ else (CFG.LLM_PERSONA if CFG.LLM_PERSONA is not None else DEFAULT_PERSONA)
157
120
  )
158
121
 
159
122
  def get_default_special_instruction_prompt(self) -> str:
160
123
  return (
161
- DEFAULT_SPECIAL_INSTRUCTION_PROMPT
162
- if self._default_special_instruction_prompt is None
163
- else self._default_special_instruction_prompt
124
+ self._default_special_instruction_prompt
125
+ if self._default_special_instruction_prompt is not None
126
+ else (
127
+ CFG.LLM_SPECIAL_INSTRUCTION_PROMPT
128
+ if CFG.LLM_SPECIAL_INSTRUCTION_PROMPT is not None
129
+ else DEFAULT_SPECIAL_INSTRUCTION_PROMPT
130
+ )
164
131
  )
165
132
 
166
133
  def get_default_summarization_prompt(self) -> str:
167
134
  return (
168
- DEFAULT_SUMMARIZATION_PROMPT
169
- if self._default_summarization_prompt is None
170
- else self._default_summarization_prompt
135
+ self._default_summarization_prompt
136
+ if self._default_summarization_prompt is not None
137
+ else (
138
+ CFG.LLM_SUMMARIZATION_PROMPT
139
+ if CFG.LLM_SUMMARIZATION_PROMPT is not None
140
+ else DEFAULT_SUMMARIZATION_PROMPT
141
+ )
171
142
  )
172
143
 
173
144
  def get_default_context_enrichment_prompt(self) -> str:
174
145
  return (
175
- DEFAULT_CONTEXT_ENRICHMENT_PROMPT
176
- if self._default_context_enrichment_prompt is None
177
- else self._default_context_enrichment_prompt
146
+ self._default_context_enrichment_prompt
147
+ if self._default_context_enrichment_prompt is not None
148
+ else (
149
+ CFG.LLM_CONTEXT_ENRICHMENT_PROMPT
150
+ if CFG.LLM_CONTEXT_ENRICHMENT_PROMPT is not None
151
+ else DEFAULT_CONTEXT_ENRICHMENT_PROMPT
152
+ )
178
153
  )
179
154
 
180
155
  def get_default_model(self) -> Model | str | None:
@@ -191,16 +166,32 @@ class LLMConfig:
191
166
  )
192
167
 
193
168
  def get_default_summarize_history(self) -> bool:
194
- return self._default_summarize_history
169
+ return (
170
+ self._default_summarize_history
171
+ if self._default_summarize_history is not None
172
+ else CFG.LLM_SUMMARIZE_HISTORY
173
+ )
195
174
 
196
175
  def get_default_history_summarization_threshold(self) -> int:
197
- return self._default_history_summarization_threshold
176
+ return (
177
+ self._default_history_summarization_threshold
178
+ if self._default_history_summarization_threshold is not None
179
+ else CFG.LLM_HISTORY_SUMMARIZATION_THRESHOLD
180
+ )
198
181
 
199
182
  def get_default_enrich_context(self) -> bool:
200
- return self._default_enrich_context
183
+ return (
184
+ self._default_enrich_context
185
+ if self._default_enrich_context is not None
186
+ else CFG.LLM_ENRICH_CONTEXT
187
+ )
201
188
 
202
189
  def get_default_context_enrichment_threshold(self) -> int:
203
- return self._default_context_enrichment_threshold
190
+ return (
191
+ self._default_context_enrichment_threshold
192
+ if self._default_context_enrichment_threshold is not None
193
+ else CFG.LLM_CONTEXT_ENRICHMENT_THRESHOLD
194
+ )
204
195
 
205
196
  def set_default_persona(self, persona: str):
206
197
  self._default_persona = persona
zrb/runner/cli.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
  from typing import Any
3
3
 
4
- from zrb.config import BANNER, VERSION, WEB_HTTP_PORT
4
+ from zrb.config import CFG
5
5
  from zrb.context.any_context import AnyContext
6
6
  from zrb.context.shared_context import SharedContext
7
7
  from zrb.group.any_group import AnyGroup
@@ -23,6 +23,13 @@ from zrb.util.string.conversion import double_quote
23
23
 
24
24
 
25
25
  class Cli(Group):
26
+
27
+ @property
28
+ def banner(self) -> str:
29
+ if self._banner is None:
30
+ return CFG.BANNER
31
+ return self._banner
32
+
26
33
  def run(self, args: list[str] = []):
27
34
  kwargs, args = self._extract_kwargs_from_args(args)
28
35
  node, node_path, args = extract_node_from_args(self, args)
@@ -147,14 +154,14 @@ class Cli(Group):
147
154
  return kwargs, residual_args
148
155
 
149
156
 
150
- cli = Cli(name="zrb", description="Your Automation Powerhouse", banner=BANNER)
157
+ cli = Cli(name="zrb", description="Your Automation Powerhouse")
151
158
 
152
159
 
153
160
  @make_task(
154
161
  name="version", description="🌟 Get current Zrb version", retries=0, group=cli
155
162
  )
156
163
  def get_version(_: AnyContext):
157
- return VERSION
164
+ return CFG.VERSION
158
165
 
159
166
 
160
167
  server_group = cli.add_group(
@@ -174,5 +181,7 @@ async def start_server(_: AnyContext):
174
181
  from uvicorn import Config, Server
175
182
 
176
183
  app = create_web_app(cli, web_config, session_state_logger)
177
- server = Server(Config(app=app, host="0.0.0.0", port=WEB_HTTP_PORT, loop="asyncio"))
184
+ server = Server(
185
+ Config(app=app, host="0.0.0.0", port=CFG.WEB_HTTP_PORT, loop="asyncio")
186
+ )
178
187
  await server.serve()
zrb/runner/web_app.py CHANGED
@@ -2,7 +2,7 @@ import asyncio
2
2
  import sys
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from zrb.config import BANNER, VERSION
5
+ from zrb.config import CFG
6
6
  from zrb.group.any_group import AnyGroup
7
7
  from zrb.runner.web_config.config import WebConfig
8
8
  from zrb.runner.web_route.docs_route import serve_docs
@@ -37,7 +37,7 @@ def create_web_app(
37
37
 
38
38
  @asynccontextmanager
39
39
  async def lifespan(app: FastAPI):
40
- for line in BANNER.split("\n") + [
40
+ for line in CFG.BANNER.split("\n") + [
41
41
  f"Zrb Server running on http://localhost:{web_config.port}"
42
42
  ]:
43
43
  print(line, file=sys.stderr)
@@ -48,7 +48,7 @@ def create_web_app(
48
48
 
49
49
  app = FastAPI(
50
50
  title="Zrb",
51
- version=VERSION,
51
+ version=CFG.VERSION,
52
52
  summary="Your Automation Powerhouse",
53
53
  lifespan=lifespan,
54
54
  docs_url=None,
@@ -1,26 +1,15 @@
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
- )
1
+ from zrb.config import CFG
13
2
  from zrb.runner.web_config.config import WebConfig
14
3
 
15
4
  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,
5
+ port=CFG.WEB_HTTP_PORT,
6
+ secret_key=CFG.WEB_SECRET_KEY,
7
+ access_token_expire_minutes=CFG.WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
8
+ refresh_token_expire_minutes=CFG.WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
9
+ access_token_cookie_name=CFG.WEB_ACCESS_TOKEN_COOKIE_NAME,
10
+ refresh_token_cookie_name=CFG.WEB_REFRESH_TOKEN_COOKIE_NAME,
11
+ enable_auth=CFG.WEB_ENABLE_AUTH,
12
+ super_admin_username=CFG.WEB_SUPER_ADMIN_USERNAME,
13
+ super_admin_password=CFG.WEB_SUPER_ADMIN_PASSWORD,
14
+ guest_username=CFG.WEB_GUEST_USERNAME,
26
15
  )
@@ -1,5 +1,6 @@
1
1
  import os
2
2
 
3
+ from zrb.config import CFG
3
4
  from zrb.group.any_group import AnyGroup
4
5
  from zrb.runner.web_schema.user import User
5
6
  from zrb.runner.web_util.html import get_html_auth_link
@@ -11,17 +12,26 @@ def show_error_page(user: User, root_group: AnyGroup, status_code: int, message:
11
12
  from fastapi.responses import HTMLResponse
12
13
 
13
14
  _DIR = os.path.dirname(__file__)
15
+ _GLOBAL_TEMPLATE = read_file(
16
+ os.path.join(os.path.dirname(_DIR), "static", "global_template.html")
17
+ )
14
18
  _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
15
19
  auth_link = get_html_auth_link(user)
16
20
  return HTMLResponse(
17
21
  fstring_format(
18
- _VIEW_TEMPLATE,
22
+ _GLOBAL_TEMPLATE,
19
23
  {
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,
24
+ "web_title": CFG.WEB_TITLE,
25
+ "content": fstring_format(
26
+ _VIEW_TEMPLATE,
27
+ {
28
+ "name": root_group.name,
29
+ "description": root_group.description,
30
+ "auth_link": auth_link,
31
+ "error_status_code": status_code,
32
+ "error_message": message,
33
+ },
34
+ ),
25
35
  },
26
36
  ),
27
37
  status_code=status_code,
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from typing import TYPE_CHECKING
3
3
 
4
+ from zrb.config import CFG
4
5
  from zrb.group.any_group import AnyGroup
5
6
  from zrb.runner.web_config.config import WebConfig
6
7
  from zrb.runner.web_util.html import (
@@ -30,22 +31,37 @@ def serve_home_page(
30
31
  @app.get("/ui", response_class=HTMLResponse, include_in_schema=False)
31
32
  @app.get("/ui/", response_class=HTMLResponse, include_in_schema=False)
32
33
  async def home_page_ui(request: Request) -> HTMLResponse:
33
-
34
34
  _DIR = os.path.dirname(__file__)
35
+ _GLOBAL_TEMPLATE = read_file(
36
+ os.path.join(os.path.dirname(_DIR), "static", "global_template.html")
37
+ )
35
38
  _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
39
+ web_title = CFG.WEB_TITLE if CFG.WEB_TITLE.strip() != "" else root_group.name
40
+ web_jargon = (
41
+ CFG.WEB_JARGON if CFG.WEB_JARGON.strip() != "" else root_group.description
42
+ )
36
43
  user = await get_user_from_request(web_config, request)
37
44
  group_info = get_html_subgroup_info(user, "/ui/", root_group)
38
45
  task_info = get_html_subtask_info(user, "/ui/", root_group)
39
46
  auth_link = get_html_auth_link(user)
40
47
  return HTMLResponse(
41
48
  fstring_format(
42
- _VIEW_TEMPLATE,
49
+ _GLOBAL_TEMPLATE,
43
50
  {
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,
51
+ "web_title": web_title,
52
+ "content": fstring_format(
53
+ _VIEW_TEMPLATE,
54
+ {
55
+ "web_title": web_title,
56
+ "web_jargon": web_jargon,
57
+ "web_homepage_intro": CFG.WEB_HOMEPAGE_INTRO,
58
+ "group_info": group_info,
59
+ "task_info": task_info,
60
+ "name": root_group.name,
61
+ "description": root_group.description,
62
+ "auth_link": auth_link,
63
+ },
64
+ ),
49
65
  },
50
66
  )
51
67
  )
@@ -1,33 +1,19 @@
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="/docs">💻 API Documentation</a></li>
20
- </ul>
21
- <ul>
22
- <li>{auth_link}</li>
23
- </ul>
24
- </nav>
25
- </hgroup>
26
- </header>
27
- <main class="container">
28
- {group_info}
29
- {task_info}
30
- </main>
31
- </body>
32
- <script src="/refresh-token.js"></script>
33
- </html>
1
+ <header class="container">
2
+ <hgroup>
3
+ <h1>{web_title}</h1>
4
+ <p>{web_jargon}</p>
5
+ <nav>
6
+ <ul>
7
+ <li><a href="/docs">💻 API Documentation</a></li>
8
+ </ul>
9
+ <ul>
10
+ <li>{auth_link}</li>
11
+ </ul>
12
+ </nav>
13
+ </hgroup>
14
+ </header>
15
+ <main class="container">
16
+ <p>{web_homepage_intro}</p>
17
+ {group_info}
18
+ {task_info}
19
+ </main>
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from typing import TYPE_CHECKING
3
3
 
4
+ from zrb.config import CFG
4
5
  from zrb.group.any_group import AnyGroup
5
6
  from zrb.runner.web_config.config import WebConfig
6
7
  from zrb.runner.web_util.html import get_html_auth_link
@@ -24,16 +25,25 @@ def serve_login_page(
24
25
  @app.get("/login", response_class=HTMLResponse, include_in_schema=False)
25
26
  async def login(request: Request) -> HTMLResponse:
26
27
  _DIR = os.path.dirname(__file__)
28
+ _GLOBAL_TEMPLATE = read_file(
29
+ os.path.join(os.path.dirname(_DIR), "static", "global_template.html")
30
+ )
27
31
  _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
28
32
  user = await get_user_from_request(web_config, request)
29
33
  auth_link = get_html_auth_link(user)
30
34
  return HTMLResponse(
31
35
  fstring_format(
32
- _VIEW_TEMPLATE,
36
+ _GLOBAL_TEMPLATE,
33
37
  {
34
- "name": root_group.name,
35
- "description": root_group.description,
36
- "auth_link": auth_link,
38
+ "web_title": CFG.WEB_TITLE,
39
+ "content": fstring_format(
40
+ _VIEW_TEMPLATE,
41
+ {
42
+ "name": root_group.name,
43
+ "description": root_group.description,
44
+ "auth_link": auth_link,
45
+ },
46
+ ),
37
47
  },
38
48
  )
39
49
  )
@@ -1,51 +1,33 @@
1
- <!doctype html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="utf-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <meta name="color-scheme" content="light dark">
8
- <link rel="stylesheet" href="/static/pico.min.css">
9
- <link rel="icon" href="/static/favicon-32x32.png" sizes="32x32" type="image/png">
10
- <title>Zrb</title>
11
- <link rel="stylesheet" href="/static/common.css">
12
- </head>
13
-
14
- <body>
15
- <header class="container">
16
- <hgroup>
17
- <h1>{name}</h1>
18
- <p>{description}</p>
19
- <nav>
20
- <ul>
21
- <li><a href="/">🏠 Home</a></li>
22
- <li><a href="/docs">💻 API Documentation</a></li>
23
- </ul>
24
- </nav>
25
- </hgroup>
26
- </header>
27
- <main class="container">
28
- <article>
29
- <h1>Login</h1>
30
- <form id="login-form" onsubmit="login(event)">
31
- <label for="username">
32
- Username
33
- <input type="text" id="username" name="username" placeholder="Enter your username" required />
34
- </label>
35
- <label for="password">
36
- Password
37
- <input type="password" id="password" name="password" placeholder="Enter your password" required />
38
- </label>
39
- <div class="button-group">
40
- <button class="primary">🔑 Login</button>
41
- <button class="secondary" onclick="window.history.back()">🔙 Cancel</button>
42
- </div>
43
- </form>
44
- <p id="error-message"></p>
45
- </article>
46
- </main>
47
- <script src="/static/login/event.js"></script>
48
- <script src="/refresh-token.js"></script>
49
- </body>
50
-
51
- </html>
1
+ <header class="container">
2
+ <hgroup>
3
+ <h1>{name}</h1>
4
+ <p>{description}</p>
5
+ <nav>
6
+ <ul>
7
+ <li><a href="/">🏠 Home</a></li>
8
+ <li><a href="/docs">💻 API Documentation</a></li>
9
+ </ul>
10
+ </nav>
11
+ </hgroup>
12
+ </header>
13
+ <main class="container">
14
+ <article>
15
+ <h1>Login</h1>
16
+ <form id="login-form" onsubmit="login(event)">
17
+ <label for="username">
18
+ Username
19
+ <input type="text" id="username" name="username" placeholder="Enter your username" required />
20
+ </label>
21
+ <label for="password">
22
+ Password
23
+ <input type="password" id="password" name="password" placeholder="Enter your password" required />
24
+ </label>
25
+ <div class="button-group">
26
+ <button class="primary">🔑 Login</button>
27
+ <button class="secondary" onclick="window.history.back()">🔙 Cancel</button>
28
+ </div>
29
+ </form>
30
+ <p id="error-message"></p>
31
+ </article>
32
+ </main>
33
+ <script src="/static/login/event.js"></script>
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from typing import TYPE_CHECKING
3
3
 
4
+ from zrb.config import CFG
4
5
  from zrb.group.any_group import AnyGroup
5
6
  from zrb.runner.web_config.config import WebConfig
6
7
  from zrb.runner.web_util.html import get_html_auth_link
@@ -24,17 +25,26 @@ def serve_logout_page(
24
25
  @app.get("/logout", response_class=HTMLResponse, include_in_schema=False)
25
26
  async def logout(request: Request) -> HTMLResponse:
26
27
  _DIR = os.path.dirname(__file__)
28
+ _GLOBAL_TEMPLATE = read_file(
29
+ os.path.join(os.path.dirname(_DIR), "static", "global_template.html")
30
+ )
27
31
  _VIEW_TEMPLATE = read_file(os.path.join(_DIR, "view.html"))
28
32
  user = await get_user_from_request(web_config, request)
29
33
  auth_link = get_html_auth_link(user)
30
34
  return HTMLResponse(
31
35
  fstring_format(
32
- _VIEW_TEMPLATE,
36
+ _GLOBAL_TEMPLATE,
33
37
  {
34
- "name": root_group.name,
35
- "description": root_group.description,
36
- "auth_link": auth_link,
37
- "user": user,
38
+ "web_title": CFG.WEB_TITLE,
39
+ "content": fstring_format(
40
+ _VIEW_TEMPLATE,
41
+ {
42
+ "name": root_group.name,
43
+ "description": root_group.description,
44
+ "auth_link": auth_link,
45
+ "user": user,
46
+ },
47
+ ),
38
48
  },
39
49
  )
40
50
  )