keepup-http 0.5.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 (43) hide show
  1. keepup_http/__init__.py +38 -0
  2. keepup_http/api_docs.py +76 -0
  3. keepup_http/body_limit.py +156 -0
  4. keepup_http/events_api.py +237 -0
  5. keepup_http/factory.py +562 -0
  6. keepup_http/lock_routes.py +155 -0
  7. keepup_http/plugin.py +53 -0
  8. keepup_http/routes.py +549 -0
  9. keepup_http/static/css/aos.css +1 -0
  10. keepup_http/static/css/main_nebula.css +232 -0
  11. keepup_http/static/css/main_new.css +869 -0
  12. keepup_http/static/css/tailwind.css +1 -0
  13. keepup_http/static/index_nebula.html +282 -0
  14. keepup_http/static/index_new.html +275 -0
  15. keepup_http/static/js/aos.js +1 -0
  16. keepup_http/static/js/feather-icons.js +13 -0
  17. keepup_http/static/js/layout_bootstrap.js +29 -0
  18. keepup_http/static/js/main_new.js +2076 -0
  19. keepup_http/static/js/tailwind.js +83 -0
  20. keepup_http/static/modules/css/background_tasks.css +233 -0
  21. keepup_http/static/modules/css/cluster.css +16 -0
  22. keepup_http/static/modules/css/event_manager.css +386 -0
  23. keepup_http/static/modules/css/integration_logs.css +33 -0
  24. keepup_http/static/modules/css/metrics.css +115 -0
  25. keepup_http/static/modules/css/modules.css +189 -0
  26. keepup_http/static/modules/css/themes.css +563 -0
  27. keepup_http/static/modules/css/users.css +278 -0
  28. keepup_http/static/modules/js/background_tasks.js +695 -0
  29. keepup_http/static/modules/js/chart.js +14 -0
  30. keepup_http/static/modules/js/chartjs-adapter-date-fns.bundle.min.js +7 -0
  31. keepup_http/static/modules/js/cluster.js +374 -0
  32. keepup_http/static/modules/js/event_manager.js +1007 -0
  33. keepup_http/static/modules/js/integration_logs.js +795 -0
  34. keepup_http/static/modules/js/metrics.js +929 -0
  35. keepup_http/static/modules/js/modules.js +1128 -0
  36. keepup_http/static/modules/js/themes.js +684 -0
  37. keepup_http/static/modules/js/users.js +898 -0
  38. keepup_http/web.py +214 -0
  39. keepup_http-0.5.0.dist-info/METADATA +29 -0
  40. keepup_http-0.5.0.dist-info/RECORD +43 -0
  41. keepup_http-0.5.0.dist-info/WHEEL +5 -0
  42. keepup_http-0.5.0.dist-info/entry_points.txt +2 -0
  43. keepup_http-0.5.0.dist-info/top_level.txt +1 -0
keepup_http/web.py ADDED
@@ -0,0 +1,214 @@
1
+ """The pages and the health of the application itself.
2
+
3
+ Everything here is served from the working directory, not from the package:
4
+ the front end and the version file are data of the deployment, and the
5
+ application says where they are through ``configure()``. The public health
6
+ answer deliberately carries nothing about the database beyond whether it
7
+ answered -- the detailed one is administrative.
8
+ """
9
+
10
+ import logging
11
+ import os
12
+ from pathlib import Path
13
+
14
+ from fastapi import Depends, HTTPException
15
+ from fastapi.responses import FileResponse, JSONResponse
16
+
17
+ from keepup import clock
18
+ from keepup.api_versions import describe
19
+ from keepup_audit.audit import incoming_requests_buffer
20
+ from keepup_auth.dependencies import get_current_admin
21
+ from keepup.instance import get_instance_id
22
+
23
+
24
+ def _replica_state() -> str:
25
+ """Whether this replica serves, from the cluster capability if it is there.
26
+
27
+ A deployment that never installed `keepup-cluster` has one replica and no
28
+ state to report (keepup-117).
29
+ """
30
+ try:
31
+ from keepup_cluster.cluster import current_state
32
+ except ImportError:
33
+ return "serving"
34
+ return current_state()
35
+ from keepup_ui.themes import config_service
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+ #: Where the front end lives, relative to the working directory.
40
+ STATIC_DIR = "static"
41
+ CLIENT_PAGE = "main.html"
42
+ VERSION_FILE = "version.json"
43
+ FAVICON_FILE = "favicon.ico"
44
+
45
+
46
+ #: The package's own front end -- the panel shell. Resolved from this file's
47
+ #: location and never from the working directory: it is data of the package, and
48
+ #: the process may have been started anywhere.
49
+ SHELL_DIR = str(Path(__file__).resolve().parent / "static")
50
+
51
+ #: The page served when the active theme names a file that may not be served.
52
+ #: Not an error page: an administrator who broke the theme still has to be able
53
+ #: to reach the panel and fix it.
54
+ DEFAULT_PANEL_PAGE = "index_new.html"
55
+
56
+
57
+ def static_page(name):
58
+ """Resolve a page name against the front ends, refusing to leave either.
59
+
60
+ Two directories, in this order: the application's, then the package's. The
61
+ application first because a theme of its own must be able to replace one of
62
+ the package's by name; the package second because that is where the shipped
63
+ panel lives once keepup is installed rather than sitting in the repository.
64
+
65
+ The name reaches here from the `visual_themes` table, so it is data, not a
66
+ constant of the code: `os.path.join(STATIC_DIR, "../config/auth.yaml")` is
67
+ a perfectly ordinary path, and FileResponse would serve it. Containment is
68
+ checked after resolution, so that `..`, a symbolic link and an absolute
69
+ name are all refused by the same rule.
70
+
71
+ Args:
72
+ name: the file name as it was stored.
73
+
74
+ Returns:
75
+ The path to serve, or None when the name leads outside both directories
76
+ or names nothing in either.
77
+ """
78
+ if not name:
79
+ return None
80
+ for root_dir in (STATIC_DIR, SHELL_DIR):
81
+ root = os.path.realpath(root_dir)
82
+ candidate = os.path.realpath(os.path.join(root_dir, name))
83
+ if candidate != root and not candidate.startswith(root + os.sep):
84
+ continue
85
+ if os.path.isfile(candidate):
86
+ return candidate
87
+ return None
88
+
89
+
90
+ def theme_page():
91
+ """The active theme's page, or the default one when it may not be served."""
92
+ name = config_service.get_active_theme_page_file()
93
+ path = static_page(name)
94
+ if path is None:
95
+ logger.error(
96
+ "The active theme names a page that is outside the front ends or "
97
+ "absent from both (%r); serving %s instead", name, DEFAULT_PANEL_PAGE)
98
+ return os.path.join(SHELL_DIR, DEFAULT_PANEL_PAGE)
99
+ return path
100
+
101
+
102
+ #: The panel pages are revalidated like the shell's assets (GatedStaticFiles
103
+ #: says the same for them): without it a browser invents a freshness lifetime
104
+ #: and keeps serving the page it cached before the upgrade -- and a page cached
105
+ #: before keepup-93 carries inline handlers, which the Content-Security-Policy
106
+ #: it is now served with refuses.
107
+ PAGE_HEADERS = {"Cache-Control": "no-cache"}
108
+
109
+
110
+ def page_response(path):
111
+ """A panel page, with the revalidation its assets already carry."""
112
+ return FileResponse(path, headers=PAGE_HEADERS)
113
+
114
+
115
+ def configure(static_dir=None, client_page=None, version_file=None, favicon_file=None):
116
+ """Supply the application's front-end paths."""
117
+ global STATIC_DIR, CLIENT_PAGE, VERSION_FILE, FAVICON_FILE
118
+ if static_dir is not None:
119
+ STATIC_DIR = static_dir
120
+ if client_page is not None:
121
+ CLIENT_PAGE = client_page
122
+ if version_file is not None:
123
+ VERSION_FILE = version_file
124
+ if favicon_file is not None:
125
+ FAVICON_FILE = favicon_file
126
+
127
+
128
+
129
+ def register_web_routes(app):
130
+ """Register the pages, the version and the health endpoints."""
131
+
132
+ @app.get("/", response_class=FileResponse)
133
+ async def read_root():
134
+ return page_response(os.path.join(STATIC_DIR, CLIENT_PAGE))
135
+ @app.get("/selfcare", response_class=FileResponse)
136
+ async def read_root_selfcare():
137
+ """Return the main page of the active visual theme."""
138
+ file_path = theme_page()
139
+ logger.debug(f"Serving {file_path} for selfcare page")
140
+ return page_response(file_path)
141
+ @app.get("/api/version", response_class=FileResponse)
142
+ async def get_version():
143
+ return FileResponse(os.path.join(STATIC_DIR, VERSION_FILE))
144
+ @app.get("/selfcare/modules/{path:path}")
145
+ async def serve_modules_app(path: str):
146
+ """Serve every path under /modules to the frontend."""
147
+ file_path = theme_page()
148
+ logger.debug(f"Serving {file_path} for selfcare page")
149
+ return page_response(file_path)
150
+ @app.get("/favicon.ico", include_in_schema=False)
151
+ async def favicon():
152
+ return FileResponse(os.path.join(STATIC_DIR, FAVICON_FILE))
153
+ @app.get("/api/health")
154
+ def health_check():
155
+ """Whether this instance is up and reaches its database -- and nothing more.
156
+
157
+ The answer is public: the deployment proxies it to the world. It used to carry
158
+ the database connection string, which named the host, port, database and user
159
+ inside the stand's network -- a network deliberately kept behind the public
160
+ proxy. Details are for an administrator, at /api/admin/health.
161
+
162
+ A replica an administrator stopped answers 503 "stopped": that is what
163
+ makes a load balancer take it out (keepup/cluster.py).
164
+ """
165
+ state = _replica_state()
166
+ if state == "stopped":
167
+ return JSONResponse(status_code=503, content={
168
+ "status": state,
169
+ "instance_id": get_instance_id(),
170
+ "timestamp": clock.utcnow().isoformat(),
171
+ })
172
+ try:
173
+ # Taken here, not at the top of the module: importing the framework
174
+ # must not load a database (keepup-127).
175
+ from keepup_db import DatabaseManagerV2
176
+
177
+ db_status = DatabaseManagerV2.test_connection()
178
+ except Exception as e:
179
+ db_status = {"success": False, "error": str(e)}
180
+
181
+ if not db_status.get("success"):
182
+ # The reason goes to the log; the caller learns that it failed, not where.
183
+ logger.error(f"Health check failed: {db_status.get('error')}")
184
+ raise HTTPException(status_code=503, detail="Service unavailable")
185
+
186
+ return {
187
+ "status": "healthy",
188
+ # Which instance answered: how one tells the public address and the stand
189
+ # are the same deployment (doc/stand_update.md). A container name, not an address.
190
+ "instance_id": get_instance_id(),
191
+ "timestamp": clock.utcnow().isoformat(),
192
+ }
193
+ @app.get("/api/versions")
194
+ async def api_versions():
195
+ """Which versions of the API this server speaks; public, read by agents at start."""
196
+ return describe()
197
+ @app.get("/api/admin/health")
198
+ def admin_health_check(admin: dict = Depends(get_current_admin)):
199
+ """The full picture for an administrator: database, its version and settings."""
200
+ try:
201
+ # Taken here, not at the top of the module: importing the framework
202
+ # must not load a database (keepup-127).
203
+ from keepup_db import DatabaseManagerV2
204
+
205
+ db_status = DatabaseManagerV2.test_connection()
206
+ except Exception as e:
207
+ db_status = {"success": False, "error": str(e)}
208
+ return {
209
+ "status": "healthy" if db_status.get("success") else "unhealthy",
210
+ "database": db_status,
211
+ "instance_id": get_instance_id(),
212
+ "timestamp": clock.utcnow().isoformat(),
213
+ "incoming_requests_buffer_size": len(incoming_requests_buffer),
214
+ }
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: keepup-http
3
+ Version: 0.5.0
4
+ Summary: The way into the application over HTTP: the application, its routes and its middleware.
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: keepup-admin>=0.4.0
8
+ Requires-Dist: keepup-db>=0.4.0
9
+ Requires-Dist: keepup-auth>=0.4.0
10
+ Requires-Dist: keepup-audit>=0.4.0
11
+ Requires-Dist: keepup-cluster>=0.4.0
12
+ Requires-Dist: keepup-modules>=0.4.0
13
+ Requires-Dist: keepup-metrics>=0.4.0
14
+ Requires-Dist: keepup-tasks>=0.4.0
15
+ Requires-Dist: keepup-ui>=0.4.0
16
+ Requires-Dist: fastapi<1,>=0.128
17
+ Requires-Dist: starlette<2,>=1.3.1
18
+ Requires-Dist: pydantic<3,>=2.11
19
+
20
+ # keepup-http
21
+
22
+ The way into the application over HTTP: the application, its routes and its middleware.
23
+
24
+ One capability of [keepup](https://github.com/FedorSergeev/keepup-admin), the framework over FastAPI for admin
25
+ panels: the base is installed as `keepup-admin` and imported as `keepup`, and
26
+ this distribution carries one part of it. It is installed on its own
27
+ (`pip install keepup-http`) or together with the base (`pip install "keepup-admin[panel]"`),
28
+ and it declares itself to the kernel through its
29
+ `[project.entry-points."keepup.plugins"]` entry point.
@@ -0,0 +1,43 @@
1
+ keepup_http/__init__.py,sha256=oaBdDuvOXIr97sciS1AKcFYeBDGMb9mQcmpTiSu6spE,1379
2
+ keepup_http/api_docs.py,sha256=rw-LGuouQUIonRP7TEeLwvJaycwHg9a8unYACTinm8Q,3211
3
+ keepup_http/body_limit.py,sha256=ExS17EauRgsRjDtfMpPunHr997OAKmwaBfBZZda3-Qc,6789
4
+ keepup_http/events_api.py,sha256=GVhL5ZMnNT4-T8Vo1FEz903hORleW0LQJ8DqDc2peOY,10318
5
+ keepup_http/factory.py,sha256=Ny7tR9Oh6MbqUNtgWdi9wWWkZM0VhuZMRWROSl4kWSI,25355
6
+ keepup_http/lock_routes.py,sha256=A5gD8w3g-xj6EXUgAMbnDtHWuKWPEYxNSY9EcY-DO9g,6052
7
+ keepup_http/plugin.py,sha256=BQtG-c2yfkbPN4YAwV_tLV0n-rNErJheIZW7U0fBKCs,2042
8
+ keepup_http/routes.py,sha256=DZbRqWiwmembhENh0PHowfk-OxfhYBM7u4K37ygRzOo,23828
9
+ keepup_http/web.py,sha256=NxWGbB2WNqpklbDmyVeeYU4wTEF6PmvY1bwX7-JBC0Y,8765
10
+ keepup_http/static/index_nebula.html,sha256=_I-3Df-I2tM0-4R2Cic8XI1MGsRrIwm9ylFm_jlaLAY,13393
11
+ keepup_http/static/index_new.html,sha256=gJrD9F3MvQDGuCN1FCJi5VQLF1QIp6kbiM7c4tnH-3k,13151
12
+ keepup_http/static/css/aos.css,sha256=GqiEX9BuR1rv5zPU5Vs2qS_NSHl1BJyBcjQYJ6ycwD4,26053
13
+ keepup_http/static/css/main_nebula.css,sha256=dg9DoXia9kM5A1dYwm1Cj5Y8bCSN-gdUGE7kkp3vLW8,7670
14
+ keepup_http/static/css/main_new.css,sha256=Q5uz63TOcH6H46I8KqwzhdrA0PEVlEeS6Is9Ub80MZA,23947
15
+ keepup_http/static/css/tailwind.css,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
16
+ keepup_http/static/js/aos.js,sha256=8mhhK6WerRskNTu3fWZ4O8xDWv8cIr5fk8QLrDhplo4,14239
17
+ keepup_http/static/js/feather-icons.js,sha256=6kZaxraaA33En9pqscWF-VclCR7_RRXfYduR2dPF5U4,76047
18
+ keepup_http/static/js/layout_bootstrap.js,sha256=qsrnIaMllpu76ZOsLtfCcMw0AHfis1CFGSbVBAYyyzU,1357
19
+ keepup_http/static/js/main_new.js,sha256=_9ZXLp2GapCAN5Ge1mDbKG2hAT2Ey408-lGrP1ht-ng,72867
20
+ keepup_http/static/js/tailwind.js,sha256=YUsXy7eac-_sNNwQBa7AtFLnApjlzx7yf9PLE2FlLDk,407278
21
+ keepup_http/static/modules/css/background_tasks.css,sha256=xEr50ttH7M1AKaIOtXffaarAPwhOXOtZ5ALDPDgR1bU,3756
22
+ keepup_http/static/modules/css/cluster.css,sha256=xUT2Ovp1928j7CFtsril-FsIQxeO6GhfyU5iRYZfdt4,444
23
+ keepup_http/static/modules/css/event_manager.css,sha256=zjBZaTpVFDcooWvr6aJ_n9g4Nmxs7k3yJk16FI3_0u0,7096
24
+ keepup_http/static/modules/css/integration_logs.css,sha256=GrcAZf56q_BmHapoplB1Bf-nA1JT5C91sNiPGKkyO8o,629
25
+ keepup_http/static/modules/css/metrics.css,sha256=BgWa_XITZvigBUf1w3kOtNROAqJwFWPgjuZmFbXor04,2018
26
+ keepup_http/static/modules/css/modules.css,sha256=tVZ5ZM9U8aw6_Us_rgrdrMk_jGx3HqOqCSbOa48Mo84,3308
27
+ keepup_http/static/modules/css/themes.css,sha256=Xe9x8e3XzrNqgg_7tN3kARxBbwRdZ6dBDhfMUn-nXtc,11208
28
+ keepup_http/static/modules/css/users.css,sha256=avR3K4-mD7nZusy4m9Plm929gS3_aABr9UaikQAznOY,4518
29
+ keepup_http/static/modules/js/background_tasks.js,sha256=zCvtKWheAno9m1Gcs4GQsWRbq75jsT779e0HLVgtnR4,28206
30
+ keepup_http/static/modules/js/chart.js,sha256=M5q_6Ip8lDTBJQZG4N44JM0epSnA0UjNFmUwj0LpA4E,208521
31
+ keepup_http/static/modules/js/chartjs-adapter-date-fns.bundle.min.js,sha256=VvDT2DpQOuVGg6w8jmJ-cKZiLFAJyPKVZt9A4LzbZ4M,50649
32
+ keepup_http/static/modules/js/cluster.js,sha256=RNML0Z5IOdvFWmHS4lyo-8pcKFZ8VzU1uLxYw66P-p0,16391
33
+ keepup_http/static/modules/js/event_manager.js,sha256=awyfis19PdAIeUL32h7dQU8h6ZUCXh33Hu1ZyURJZwk,34969
34
+ keepup_http/static/modules/js/integration_logs.js,sha256=AaPwkw2WKCO8n0ejONxGcuYfxaiDdcN_SX2vK4YSiE4,34056
35
+ keepup_http/static/modules/js/metrics.js,sha256=Z3NrwXIoH9Gsvx0NuYIibjLIZCaSBHdDq9Q0wgNnt6w,35637
36
+ keepup_http/static/modules/js/modules.js,sha256=qEbz-swdOOgzrHWKoOFBNt_Hy5E8-bBF7HKu3v5j_5c,50249
37
+ keepup_http/static/modules/js/themes.js,sha256=PAbGHjIU4FqC4COptapP6NstrA6_jAHPEV6J6bHuJIU,26530
38
+ keepup_http/static/modules/js/users.js,sha256=GlifR_XDwKisqn-sjMrRW9f7LlGFqYXUF4JZVUmM3sQ,38410
39
+ keepup_http-0.5.0.dist-info/METADATA,sha256=LnNMQM8xzGRci4VxGw42EbH2SQDT_czx_io1PftXN-M,1191
40
+ keepup_http-0.5.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
41
+ keepup_http-0.5.0.dist-info/entry_points.txt,sha256=gS1ctzDW3Dw-nKFqmyN8Z5rrR8qm_vu-vXpfM-rvOfo,47
42
+ keepup_http-0.5.0.dist-info/top_level.txt,sha256=BNfVgntXFDeEe-9ZSZXszO02YwuihPOb6n73yf5lXr8,12
43
+ keepup_http-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [keepup.plugins]
2
+ http = keepup_http:HttpPlugin
@@ -0,0 +1 @@
1
+ keepup_http