sovereign 1.0.0b123__py3-none-any.whl → 1.0.0b134__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.
- sovereign/app.py +1 -1
- sovereign/cache/__init__.py +182 -0
- sovereign/cache/backends/__init__.py +110 -0
- sovereign/cache/backends/s3.py +139 -0
- sovereign/cache/filesystem.py +42 -0
- sovereign/cache/types.py +15 -0
- sovereign/context.py +20 -18
- sovereign/events.py +49 -0
- sovereign/middlewares.py +1 -1
- sovereign/rendering.py +74 -35
- sovereign/schemas.py +112 -110
- sovereign/server.py +4 -3
- sovereign/sources/poller.py +20 -4
- sovereign/statistics.py +1 -1
- sovereign/templates/base.html +59 -46
- sovereign/templates/resources.html +40 -835
- sovereign/utils/mock.py +7 -3
- sovereign/views/healthchecks.py +1 -1
- sovereign/views/interface.py +34 -15
- sovereign/worker.py +87 -46
- {sovereign-1.0.0b123.dist-info → sovereign-1.0.0b134.dist-info}/METADATA +4 -5
- {sovereign-1.0.0b123.dist-info → sovereign-1.0.0b134.dist-info}/RECORD +33 -24
- {sovereign-1.0.0b123.dist-info → sovereign-1.0.0b134.dist-info}/WHEEL +1 -1
- {sovereign-1.0.0b123.dist-info → sovereign-1.0.0b134.dist-info}/entry_points.txt +3 -0
- sovereign_files/__init__.py +0 -0
- sovereign_files/static/darkmode.js +51 -0
- sovereign_files/static/node_expression.js +42 -0
- sovereign_files/static/resources.css +246 -0
- sovereign_files/static/resources.js +642 -0
- sovereign_files/static/sass/style.scss +33 -0
- sovereign_files/static/style.css +16143 -0
- sovereign_files/static/style.css.map +1 -0
- sovereign/cache.py +0 -133
- sovereign/static/node_expression.js +0 -16
- sovereign/static/sass/style.scss +0 -27
- sovereign/static/style.css +0 -13553
- sovereign-1.0.0b123.dist-info/LICENSE.txt +0 -13
- {sovereign → sovereign_files}/static/panel.js +0 -0
sovereign/utils/mock.py
CHANGED
|
@@ -7,12 +7,16 @@ from sovereign.schemas import DiscoveryRequest, Node, Locality, Status
|
|
|
7
7
|
scrub = re.compile(r"[^a-zA-Z_\.]")
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
class NodeExpressionError(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
def mock_discovery_request(
|
|
11
15
|
api_version: Optional[str] = "V3",
|
|
12
16
|
resource_type: Optional[str] = None,
|
|
13
17
|
resource_names: Optional[List[str] | str] = None,
|
|
14
18
|
region: Optional[str] = "none",
|
|
15
|
-
version: Optional[str] = "
|
|
19
|
+
version: Optional[str] = "<envoy_version>",
|
|
16
20
|
metadata: Optional[Dict[str, str]] = None,
|
|
17
21
|
error_message: Optional[str] = None,
|
|
18
22
|
expressions: Optional[list[str]] = None,
|
|
@@ -54,7 +58,7 @@ def set_node_expressions(node, expressions):
|
|
|
54
58
|
field, value = re.split(r"\s*=\s*", expr, maxsplit=1)
|
|
55
59
|
value = f'"{value}"'
|
|
56
60
|
except ValueError:
|
|
57
|
-
raise
|
|
61
|
+
raise NodeExpressionError(f"Invalid node filter format: {expr}")
|
|
58
62
|
|
|
59
63
|
field = scrub.sub("", field)
|
|
60
64
|
parts = field.split(".")
|
|
@@ -62,7 +66,7 @@ def set_node_expressions(node, expressions):
|
|
|
62
66
|
try:
|
|
63
67
|
value = ast.literal_eval(value)
|
|
64
68
|
except Exception as e:
|
|
65
|
-
raise
|
|
69
|
+
raise NodeExpressionError(f"Invalid node filter value: {value}") from e
|
|
66
70
|
|
|
67
71
|
current = node
|
|
68
72
|
for part in parts[:-1]:
|
sovereign/views/healthchecks.py
CHANGED
|
@@ -31,7 +31,7 @@ async def deep_check(response: Response) -> List[str]:
|
|
|
31
31
|
for template in list(XDS_TEMPLATES["default"].keys()):
|
|
32
32
|
try:
|
|
33
33
|
req = mock_discovery_request("v3", template, expressions=["cluster=*"])
|
|
34
|
-
cache.
|
|
34
|
+
await cache.blocking_read(req)
|
|
35
35
|
# pylint: disable=broad-except
|
|
36
36
|
except Exception as e:
|
|
37
37
|
ret.append(f"Failed {template}: {str(e)}")
|
sovereign/views/interface.py
CHANGED
|
@@ -7,10 +7,10 @@ from fastapi.encoders import jsonable_encoder
|
|
|
7
7
|
from fastapi.requests import Request
|
|
8
8
|
from fastapi.responses import HTMLResponse, JSONResponse, Response
|
|
9
9
|
|
|
10
|
-
from sovereign import html_templates, cache
|
|
10
|
+
from sovereign import html_templates, cache, __version__
|
|
11
11
|
from sovereign.schemas import DiscoveryTypes, XDS_TEMPLATES
|
|
12
12
|
from sovereign.response_class import json_response_class
|
|
13
|
-
from sovereign.utils.mock import mock_discovery_request
|
|
13
|
+
from sovereign.utils.mock import NodeExpressionError, mock_discovery_request
|
|
14
14
|
|
|
15
15
|
router = APIRouter()
|
|
16
16
|
|
|
@@ -25,9 +25,7 @@ async def ui_main(request: Request) -> HTMLResponse:
|
|
|
25
25
|
request=request,
|
|
26
26
|
name="base.html",
|
|
27
27
|
media_type="text/html",
|
|
28
|
-
context={
|
|
29
|
-
"all_types": all_types,
|
|
30
|
-
},
|
|
28
|
+
context={"all_types": all_types, "sovereign_version": __version__},
|
|
31
29
|
)
|
|
32
30
|
except IndexError:
|
|
33
31
|
return html_templates.TemplateResponse(
|
|
@@ -36,9 +34,12 @@ async def ui_main(request: Request) -> HTMLResponse:
|
|
|
36
34
|
media_type="text/html",
|
|
37
35
|
context={
|
|
38
36
|
"title": "No resource types configured",
|
|
39
|
-
"message":
|
|
40
|
-
|
|
37
|
+
"message": (
|
|
38
|
+
"A template should be defined for every resource "
|
|
39
|
+
"type that you want your envoy proxies to discover."
|
|
40
|
+
),
|
|
41
41
|
"doc_link": "https://developer.atlassian.com/platform/sovereign/tutorial/templates/#templates",
|
|
42
|
+
"sovereign_version": __version__,
|
|
42
43
|
},
|
|
43
44
|
)
|
|
44
45
|
|
|
@@ -63,18 +64,31 @@ async def resources(
|
|
|
63
64
|
) -> HTMLResponse:
|
|
64
65
|
ret: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
65
66
|
response = None
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
try:
|
|
68
|
+
mock_request = mock_discovery_request(
|
|
69
|
+
api_version,
|
|
70
|
+
xds_type,
|
|
71
|
+
version=envoy_version,
|
|
72
|
+
region=region,
|
|
73
|
+
expressions=node_expression.split(),
|
|
74
|
+
)
|
|
75
|
+
clear_cookie = False
|
|
76
|
+
error = None
|
|
77
|
+
except NodeExpressionError as e:
|
|
78
|
+
mock_request = mock_discovery_request(
|
|
79
|
+
api_version,
|
|
80
|
+
xds_type,
|
|
81
|
+
version=envoy_version,
|
|
82
|
+
region=region,
|
|
83
|
+
)
|
|
84
|
+
clear_cookie = True
|
|
85
|
+
error = str(e)
|
|
86
|
+
|
|
73
87
|
response = await cache.blocking_read(mock_request)
|
|
74
88
|
if response:
|
|
75
89
|
ret["resources"] = json.loads(response.text).get("resources", [])
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
resp = html_templates.TemplateResponse(
|
|
78
92
|
request=request,
|
|
79
93
|
name="resources.html",
|
|
80
94
|
media_type="text/html",
|
|
@@ -87,8 +101,13 @@ async def resources(
|
|
|
87
101
|
"all_types": all_types,
|
|
88
102
|
"version": envoy_version,
|
|
89
103
|
"available_versions": list(XDS_TEMPLATES.keys()),
|
|
104
|
+
"error": error,
|
|
105
|
+
"sovereign_version": __version__,
|
|
90
106
|
},
|
|
91
107
|
)
|
|
108
|
+
if clear_cookie:
|
|
109
|
+
resp.delete_cookie("node_expression", path="/ui/resources/")
|
|
110
|
+
return resp
|
|
92
111
|
|
|
93
112
|
|
|
94
113
|
@router.get(
|
sovereign/worker.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
|
-
from multiprocessing import Process, cpu_count
|
|
2
|
+
from typing import Optional, final
|
|
4
3
|
from contextlib import asynccontextmanager
|
|
5
4
|
|
|
6
5
|
from fastapi import FastAPI, Body
|
|
@@ -17,12 +16,50 @@ from sovereign import (
|
|
|
17
16
|
)
|
|
18
17
|
from sovereign.sources import SourcePoller
|
|
19
18
|
from sovereign.schemas import RegisterClientRequest, DiscoveryRequest
|
|
20
|
-
from sovereign.
|
|
19
|
+
from sovereign.events import bus, Topic
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
ClientId = str
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
OnDemandJob = tuple[ClientId, DiscoveryRequest]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@final
|
|
27
|
+
class RenderQueue:
|
|
28
|
+
def __init__(self, maxsize: int = 0):
|
|
29
|
+
self._queue: asyncio.Queue[OnDemandJob] = asyncio.Queue(maxsize)
|
|
30
|
+
self._set: set[ClientId] = set()
|
|
31
|
+
self._lock = asyncio.Lock()
|
|
32
|
+
|
|
33
|
+
async def put(self, item: OnDemandJob):
|
|
34
|
+
id_ = item[0]
|
|
35
|
+
async with self._lock:
|
|
36
|
+
if id_ not in self._set:
|
|
37
|
+
await self._queue.put(item)
|
|
38
|
+
self._set.add(id_)
|
|
39
|
+
|
|
40
|
+
def put_nowait(self, item: OnDemandJob):
|
|
41
|
+
id_ = item[0]
|
|
42
|
+
if id_ in self._set:
|
|
43
|
+
return
|
|
44
|
+
if self._queue.full():
|
|
45
|
+
raise asyncio.QueueFull
|
|
46
|
+
self._queue.put_nowait(item)
|
|
47
|
+
self._set.add(id_)
|
|
48
|
+
|
|
49
|
+
async def get(self):
|
|
50
|
+
item = await self._queue.get()
|
|
51
|
+
async with self._lock:
|
|
52
|
+
self._set.remove(item[0])
|
|
53
|
+
return item
|
|
54
|
+
|
|
55
|
+
def full(self):
|
|
56
|
+
return self._queue.full()
|
|
57
|
+
|
|
58
|
+
def task_done(self):
|
|
59
|
+
self._queue.task_done()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
ONDEMAND = RenderQueue()
|
|
26
63
|
|
|
27
64
|
|
|
28
65
|
def hidden_field(*args, **kwargs):
|
|
@@ -60,70 +97,69 @@ if config.sources is not None:
|
|
|
60
97
|
context_middleware.append(poller.add_to_context)
|
|
61
98
|
|
|
62
99
|
|
|
63
|
-
def render(job: rendering.RenderJob):
|
|
64
|
-
log.debug(f"Spawning render process for {job.id}")
|
|
65
|
-
process = Process(target=rendering.generate, args=[job])
|
|
66
|
-
process.start()
|
|
67
|
-
return process
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
async def submit_render(job: rendering.RenderJob):
|
|
71
|
-
async with RENDER_SEMAPHORE:
|
|
72
|
-
process = render(job)
|
|
73
|
-
# Wait for the process to complete to ensure semaphore is held
|
|
74
|
-
# until the actual rendering work is done
|
|
75
|
-
await asyncio.get_event_loop().run_in_executor(None, process.join)
|
|
76
|
-
|
|
77
|
-
|
|
78
100
|
async def render_on_event():
|
|
101
|
+
subscription = bus.subscribe(Topic.CONTEXT)
|
|
79
102
|
while True:
|
|
80
103
|
# block forever until new context arrives
|
|
81
|
-
await
|
|
82
|
-
|
|
104
|
+
event = await subscription.get()
|
|
105
|
+
context_name = event.metadata.get("name")
|
|
106
|
+
|
|
107
|
+
log.debug(event.message)
|
|
83
108
|
try:
|
|
84
109
|
if registered := cache.clients():
|
|
85
|
-
|
|
86
|
-
jobs = [
|
|
87
|
-
rendering.RenderJob(
|
|
88
|
-
id=client,
|
|
89
|
-
request=request,
|
|
90
|
-
context=template_context.get_context(request),
|
|
91
|
-
)
|
|
92
|
-
for client, request in registered
|
|
93
|
-
]
|
|
94
|
-
tasks = [submit_render(job) for job in jobs]
|
|
95
|
-
size = len(tasks)
|
|
110
|
+
size = len(registered)
|
|
96
111
|
stats.increment("template.render_on_event", tags=[f"batch_size:{size}"])
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
|
|
113
|
+
for client, request in registered:
|
|
114
|
+
if context_name in request.template.depends_on:
|
|
115
|
+
log.debug(f"Rendering template for {request}")
|
|
116
|
+
job = rendering.RenderJob(
|
|
117
|
+
id=client,
|
|
118
|
+
request=request,
|
|
119
|
+
context=template_context.get_context(request),
|
|
120
|
+
)
|
|
121
|
+
job.submit()
|
|
122
|
+
|
|
99
123
|
finally:
|
|
100
|
-
|
|
124
|
+
await asyncio.sleep(config.template_context.cooldown)
|
|
101
125
|
|
|
102
126
|
|
|
103
127
|
async def render_on_demand():
|
|
104
128
|
while True:
|
|
105
129
|
id, request = await ONDEMAND.get()
|
|
106
130
|
stats.increment("template.render_on_demand")
|
|
107
|
-
log.debug(
|
|
131
|
+
log.debug(
|
|
132
|
+
f"Received on-demand request to render templates for {id} ({request})"
|
|
133
|
+
)
|
|
108
134
|
job = rendering.RenderJob(
|
|
109
135
|
id=id, request=request, context=template_context.get_context(request)
|
|
110
136
|
)
|
|
111
|
-
|
|
137
|
+
job.submit()
|
|
112
138
|
ONDEMAND.task_done()
|
|
113
139
|
|
|
114
140
|
|
|
141
|
+
async def monitor_render_queue():
|
|
142
|
+
"""Periodically report render queue size metrics"""
|
|
143
|
+
while True:
|
|
144
|
+
await asyncio.sleep(10)
|
|
145
|
+
stats.gauge("template.on_demand_queue_size", ONDEMAND._queue.qsize())
|
|
146
|
+
|
|
147
|
+
|
|
115
148
|
@asynccontextmanager
|
|
116
149
|
async def lifespan(_: FastAPI):
|
|
117
150
|
# Template Rendering
|
|
118
151
|
log.debug("Starting rendering loops")
|
|
119
152
|
asyncio.create_task(render_on_event())
|
|
120
153
|
asyncio.create_task(render_on_demand())
|
|
154
|
+
asyncio.create_task(monitor_render_queue())
|
|
121
155
|
|
|
122
156
|
# Template context
|
|
157
|
+
subscription = bus.subscribe(Topic.CONTEXT)
|
|
123
158
|
log.debug("Starting context loop")
|
|
124
159
|
template_context.middleware = context_middleware
|
|
125
160
|
asyncio.create_task(template_context.start())
|
|
126
|
-
await
|
|
161
|
+
event = await subscription.get()
|
|
162
|
+
log.debug(event.message)
|
|
127
163
|
|
|
128
164
|
# Source polling
|
|
129
165
|
if poller is not None:
|
|
@@ -156,12 +192,17 @@ async def client_add(
|
|
|
156
192
|
registration: RegisterClientRequest = Body(...),
|
|
157
193
|
):
|
|
158
194
|
xds = registration.request
|
|
159
|
-
if
|
|
160
|
-
log.debug(f"
|
|
161
|
-
ONDEMAND.put_nowait(await cache.register(xds))
|
|
162
|
-
stats.increment("client.registration", tags=["status:registered"])
|
|
163
|
-
return "Registering", 202
|
|
164
|
-
else:
|
|
165
|
-
log.debug("Client already registered")
|
|
195
|
+
if cache.registered(xds):
|
|
196
|
+
log.debug(f"Client already registered {xds=}")
|
|
166
197
|
stats.increment("client.registration", tags=["status:exists"])
|
|
167
198
|
return "Registered", 200
|
|
199
|
+
else:
|
|
200
|
+
id, req = cache.register(xds)
|
|
201
|
+
log.debug(f"Received registration for new client {xds}, {id=}")
|
|
202
|
+
try:
|
|
203
|
+
ONDEMAND.put_nowait((id, req))
|
|
204
|
+
except asyncio.QueueFull:
|
|
205
|
+
stats.increment("client.registration", tags=["status:queue_full"])
|
|
206
|
+
return "Slow down :(", 429
|
|
207
|
+
stats.increment("client.registration", tags=["status:registered"])
|
|
208
|
+
return "Registering", 202
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: sovereign
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b134
|
|
4
4
|
Summary: Envoy Proxy control-plane written in Python
|
|
5
|
-
Home-page: https://pypi.org/project/sovereign/
|
|
6
5
|
License: Apache-2.0
|
|
7
6
|
Keywords: envoy,envoyproxy,control-plane,management,server
|
|
8
7
|
Author: Vasili Syrakis
|
|
@@ -20,6 +19,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.8
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -37,7 +37,6 @@ Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
|
|
|
37
37
|
Requires-Dist: boto3 (>=1.28.62,<2.0.0) ; extra == "boto"
|
|
38
38
|
Requires-Dist: cachelib (>=0.10.2,<0.11.0)
|
|
39
39
|
Requires-Dist: cachetools (>=5.3.2,<6.0.0)
|
|
40
|
-
Requires-Dist: cashews[redis] (>=6.3.0,<7.0.0) ; extra == "caching"
|
|
41
40
|
Requires-Dist: croniter (>=1.4.1,<2.0.0)
|
|
42
41
|
Requires-Dist: cryptography (>=45.0.2)
|
|
43
42
|
Requires-Dist: datadog (>=0.50.1) ; extra == "statsd"
|
|
@@ -49,7 +48,6 @@ Requires-Dist: jmespath (>=1.0.1,<2.0.0)
|
|
|
49
48
|
Requires-Dist: orjson (>=3.9.15,<4.0.0) ; extra == "orjson"
|
|
50
49
|
Requires-Dist: pydantic (>=2.7.2,<3.0.0)
|
|
51
50
|
Requires-Dist: pydantic-settings (<2.6.0)
|
|
52
|
-
Requires-Dist: redis (<=5.0.0)
|
|
53
51
|
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
54
52
|
Requires-Dist: sentry-sdk (>=2.14.0,<3.0.0) ; extra == "sentry"
|
|
55
53
|
Requires-Dist: starlette (>=0.47.2,<0.48.0)
|
|
@@ -60,6 +58,7 @@ Requires-Dist: ujson (>=5.8.0,<6.0.0) ; extra == "ujson"
|
|
|
60
58
|
Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
|
|
61
59
|
Requires-Dist: uvloop (>=0.19.0,<0.20.0)
|
|
62
60
|
Project-URL: Documentation, https://developer.atlassian.com/platform/sovereign/
|
|
61
|
+
Project-URL: Homepage, https://pypi.org/project/sovereign/
|
|
63
62
|
Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
|
|
64
63
|
Description-Content-Type: text/markdown
|
|
65
64
|
|
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
sovereign/__init__.py,sha256=m8MVzaMSW4AvAqHvUAsXFdp8Oas5oQ8X7BcVt7Hfcik,1431
|
|
2
|
-
sovereign/app.py,sha256=
|
|
3
|
-
sovereign/cache.py,sha256=
|
|
2
|
+
sovereign/app.py,sha256=LUsy4NgOddTxyC35lqezyDTG-_HMJZ5Q4b1GRv5NpgM,4882
|
|
3
|
+
sovereign/cache/__init__.py,sha256=963_hdG4_HV8Kj-LYj7HM0VlL1sJF7F06dCPQdiHv4k,6228
|
|
4
|
+
sovereign/cache/backends/__init__.py,sha256=6mPpf9OzyCBvRqpZzIBNy6jrDimEmg8yIiUYpNVTkQk,3160
|
|
5
|
+
sovereign/cache/backends/s3.py,sha256=y8j-YGHWMWet_DGEH3dBtkolLHhgGvSS0nCtRuPbIb8,4991
|
|
6
|
+
sovereign/cache/filesystem.py,sha256=83hyaRVTcueLgghgE50E88azkF4hTyIhzii9Plw1KmQ,1354
|
|
7
|
+
sovereign/cache/types.py,sha256=RXLv5fTlDOOvkkycyYuDbq4WL7X7KaEslp-Thbz7o2k,243
|
|
4
8
|
sovereign/constants.py,sha256=qdWD1lTvkaW5JGF7TmZhfksQHlRAJFVqbG7v6JQA9k8,46
|
|
5
|
-
sovereign/context.py,sha256=
|
|
9
|
+
sovereign/context.py,sha256=hnohbF4R-L3Kve5JKWHo9UfA4WGBCtwfq5m6xgMT8ak,8788
|
|
6
10
|
sovereign/dynamic_config/__init__.py,sha256=0hrI9Y-FzDywEM9Lu6i2mPFhs1c47C096R1B_-E3sKA,3161
|
|
7
11
|
sovereign/dynamic_config/deser.py,sha256=N3iUvDpuNHWjxUbGFydMVKicx4o8DyfvNukorqnQdt8,1834
|
|
8
12
|
sovereign/dynamic_config/loaders.py,sha256=gPkxTL7gep20HIMRvjgOqAdUWqtb3970VBCAcUrIM4c,2915
|
|
9
13
|
sovereign/error_info.py,sha256=r2KXBYq9Fo7AI2pmIpATWFm0pykr2MqfrKH0WWW5Sfk,1488
|
|
14
|
+
sovereign/events.py,sha256=8WEIJo0C7mnx-pLjOTXPUFWYySvVXqDsj6p2e9LR9Jg,1203
|
|
10
15
|
sovereign/logging/access_logger.py,sha256=G-R6kSPDQlrunSh34qXIT3LwbamAhdASuPgPOaXCRdM,2983
|
|
11
16
|
sovereign/logging/application_logger.py,sha256=HjrGTi2zZ06AaToDVdSv4MNIF6aWN6vFW5heAdfqwlk,1800
|
|
12
17
|
sovereign/logging/base_logger.py,sha256=ScOzHs8Rt1RZaUZGvaJSAlDEjD0BxkD5sLKSm2GgM0I,1243
|
|
13
18
|
sovereign/logging/bootstrapper.py,sha256=gWFzIVsfeMdv7-d2Z6Fiw7J0xcuZzc4z2F4Iqn1KG30,1296
|
|
14
19
|
sovereign/logging/types.py,sha256=rGqJAEVvgvzHy4aPfvEH6yQ-yblXNkEcWG7G8l9ALEA,282
|
|
15
|
-
sovereign/middlewares.py,sha256=
|
|
20
|
+
sovereign/middlewares.py,sha256=6w4JpvtNGvQA4rocQsYQjuu-ckhpKT6gKYA16T-kiqA,3082
|
|
16
21
|
sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
22
|
sovereign/modifiers/lib.py,sha256=Cx0VrpTKbSjb3YmHyG4Jy6YEaPlrwpeqNaom3zu1_hw,2885
|
|
18
|
-
sovereign/rendering.py,sha256=
|
|
23
|
+
sovereign/rendering.py,sha256=NmdUUrOZMffFOGNmZ4AhrBe1BxVsCeITrL1Hds6Iv0k,6546
|
|
19
24
|
sovereign/response_class.py,sha256=beMAFV-4L6DwyWzJzy71GkEW4gb7fzH1jd8-Tul13cU,427
|
|
20
|
-
sovereign/schemas.py,sha256=
|
|
21
|
-
sovereign/server.py,sha256=
|
|
25
|
+
sovereign/schemas.py,sha256=iYb9l1ZUy2YLwQ41cyqM9qXyqPl40dyIIAuqk9cxMYA,37565
|
|
26
|
+
sovereign/server.py,sha256=GAuBj73fpfZH760dDgVZFfI_2aKj3QHFv1JaVm4Eqow,2979
|
|
22
27
|
sovereign/sources/__init__.py,sha256=g9hEpFk8j5i1ApHQpbc9giTyJW41Ppgsqv5P9zGxOJk,78
|
|
23
28
|
sovereign/sources/file.py,sha256=prUThsDCSPNwZaZpkKXhAm-GVRZWbBoGKGU0It4HHXs,690
|
|
24
29
|
sovereign/sources/inline.py,sha256=pP77m7bHjqE3sSoqZthcuw1ARVMf9gooVwbz4B8OAek,1003
|
|
25
30
|
sovereign/sources/lib.py,sha256=0hk_G6mKJrB65WokVZnqF5kdJ3vsQZMNPuJqJO0mBsI,1031
|
|
26
|
-
sovereign/sources/poller.py,sha256=
|
|
27
|
-
sovereign/
|
|
28
|
-
sovereign/
|
|
29
|
-
sovereign/static/sass/style.scss,sha256=tPHPEm3sZeBFGDyyn3pHcA-nbaKT-h-UsSTsf6dHNDU,1158
|
|
30
|
-
sovereign/static/style.css,sha256=vG8HPsbCbPIZfHgy7gSeof97Pnp0okkyaXyJzIEEW-8,447517
|
|
31
|
-
sovereign/statistics.py,sha256=QhDB0bs5kZDGjy248AOIv_bzNbz_c2U7xmJ0hoUNOmw,2033
|
|
32
|
-
sovereign/templates/base.html,sha256=5vw3-NmN291pXRdArpCwhSce9bAYBWCJVRhvO5EmE9g,2296
|
|
31
|
+
sovereign/sources/poller.py,sha256=RPAyCKJ16k-195ugnR_UM6UtzpHOqvkzbpyY759WRLc,18994
|
|
32
|
+
sovereign/statistics.py,sha256=xyWY_VEyIHzSmptPzLDx7kmP4J3roZkJX_CHA6RZlHo,2038
|
|
33
|
+
sovereign/templates/base.html,sha256=MMhhvvClTixKibYfhXm8Ezx6ttu6Sqki44niciCPMO4,2990
|
|
33
34
|
sovereign/templates/err.html,sha256=a3cEzOqyqWOIe3YxfTEjkxbTfxBxq1knD6GwzEFljfs,603
|
|
34
|
-
sovereign/templates/resources.html,sha256=
|
|
35
|
+
sovereign/templates/resources.html,sha256=5MfXHW8s3tAWda66Q48zVgDhZNLwHGsdCKkKHLZohIs,10420
|
|
35
36
|
sovereign/testing/loaders.py,sha256=mcmErhI9ZkJUBZl8jv2qP-PCBRFeAIgyBFlfCgU4Vvk,199
|
|
36
37
|
sovereign/testing/modifiers.py,sha256=7_c2hWXn_sYJ6997N1_uSWtClOikcOzu1yRCY56-l-4,361
|
|
37
38
|
sovereign/tracing.py,sha256=Xo3npgh6yesACSlynv9j6qnXxvYEBzXv5LL4Zkc1QDw,2446
|
|
@@ -47,7 +48,7 @@ sovereign/utils/crypto/suites/fernet_cipher.py,sha256=rP6M5ys1vctyadOxDGNFoyerWP
|
|
|
47
48
|
sovereign/utils/dictupdate.py,sha256=Bi7QaC7en-k3EOepwNJqpOKRNBgp6ZsBZVOvH_0nMtc,2558
|
|
48
49
|
sovereign/utils/eds.py,sha256=sCEDj1y-0Crs40cHZLiPGVb7ed1f8vFqgHLY5R2LMbw,4377
|
|
49
50
|
sovereign/utils/entry_point_loader.py,sha256=BEVodk-um70RvT1nSOu_IB-hr1K4ppthXod0VZEiZJ8,526
|
|
50
|
-
sovereign/utils/mock.py,sha256=
|
|
51
|
+
sovereign/utils/mock.py,sha256=UyfURjsR0RVU7Xj_fiagI3IpcZjlEwaY7mx0iuxGK-k,2397
|
|
51
52
|
sovereign/utils/resources.py,sha256=rPrWgcIt4YhV-Dz88_kr5WrQNiSKt-jTlOZ8EIJxJx8,472
|
|
52
53
|
sovereign/utils/templates.py,sha256=FE_H_oE7VrS3X_VN1z_g10b9-rpmi1_gL-cMxi5XtXU,1057
|
|
53
54
|
sovereign/utils/timer.py,sha256=_dUtEasj0BKbWYuQ_T3HFIyjurXXj-La-dNSMAwKMSo,795
|
|
@@ -57,11 +58,19 @@ sovereign/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
57
58
|
sovereign/views/api.py,sha256=jKVjSvi0Tr1HHix3hc0H8yGZoyDind2sJ4w7a4pJvy0,2168
|
|
58
59
|
sovereign/views/crypto.py,sha256=7y0eHWtt-bbr2CwHEkH7odPaJ1IEviU-71U-MYJD0Kc,3360
|
|
59
60
|
sovereign/views/discovery.py,sha256=B_D1ckfbN1dSKBvuFCTyfB79GUUriCADTB53OwZ8D4Q,2409
|
|
60
|
-
sovereign/views/healthchecks.py,sha256=
|
|
61
|
-
sovereign/views/interface.py,sha256=
|
|
62
|
-
sovereign/worker.py,sha256=
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
sovereign/views/healthchecks.py,sha256=ByJORGPxkaq0P2Mb1uy6qciwH0qZpCaThXEdRaHwrws,1741
|
|
62
|
+
sovereign/views/interface.py,sha256=o6DaOqoh2M09_SsZrCOxr9rCVxMUZHXRXj4TNq800Ho,6999
|
|
63
|
+
sovereign/worker.py,sha256=HD1otdAxgPiQrkyy_FNmCZDG_Qb9XOpiPQk_-KRP4Uk,6162
|
|
64
|
+
sovereign_files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
+
sovereign_files/static/darkmode.js,sha256=3ip-eKGctDvNhN7UgmaHhzls7r5qIY-Jvh2EpefHbQ0,1449
|
|
66
|
+
sovereign_files/static/node_expression.js,sha256=GKxKTSRc_96IbL3H4L_31ueJFXq4N7scm5R1RNqxP24,1489
|
|
67
|
+
sovereign_files/static/panel.js,sha256=i5mGExjv-I4Gtt9dQiTyFwPZa8pg5rXeuTeidXNUiTE,2695
|
|
68
|
+
sovereign_files/static/resources.css,sha256=Rt_ir_FkoI-VIAOqPhk0vILy8kB2egAYbQU26SOs1io,4500
|
|
69
|
+
sovereign_files/static/resources.js,sha256=-TaXZ6tohyKA1SkX5YwrTcV5M8mOZ68cvEXpvZWznTo,24506
|
|
70
|
+
sovereign_files/static/sass/style.scss,sha256=LdGXXuHi_tyMc7XhijIOrlIxyfLt827AAs2Z7DYpFpg,990
|
|
71
|
+
sovereign_files/static/style.css,sha256=kmvkJ2820RKehWxhddkucbgFkvnpUgBMteOtpEuXjvQ,601347
|
|
72
|
+
sovereign_files/static/style.css.map,sha256=h1ufjfDVX-8z-FuJqFG2-U9AVdi66U-e8uyiGdUZjDw,66576
|
|
73
|
+
sovereign-1.0.0b134.dist-info/METADATA,sha256=y-8pqkfWk5pLl0lm07XUzKl4PxmWOTMD63DRXgw1ghI,6268
|
|
74
|
+
sovereign-1.0.0b134.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
75
|
+
sovereign-1.0.0b134.dist-info/entry_points.txt,sha256=BhOMKka7D5WOnfEB_XWWFDNlejKOB4zax-HGu-_uL9k,1473
|
|
76
|
+
sovereign-1.0.0b134.dist-info/RECORD,,
|
|
@@ -3,6 +3,9 @@ sovereign=sovereign.server:main
|
|
|
3
3
|
sovereign-web=sovereign.server:web
|
|
4
4
|
sovereign-worker=sovereign.server:worker
|
|
5
5
|
|
|
6
|
+
[sovereign.cache.backends]
|
|
7
|
+
s3=sovereign.cache.backends.s3:S3Backend
|
|
8
|
+
|
|
6
9
|
[sovereign.deserializers]
|
|
7
10
|
jinja=sovereign.dynamic_config.deser:JinjaDeserializer
|
|
8
11
|
jinja2=sovereign.dynamic_config.deser:JinjaDeserializer
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
+
const darkmode = "theme-dark";
|
|
3
|
+
const lightmode = "theme-light";
|
|
4
|
+
const toggle = document.getElementById('dark-mode-toggle');
|
|
5
|
+
const htmlTag = document.documentElement;
|
|
6
|
+
|
|
7
|
+
function preferredTheme() {
|
|
8
|
+
const preference = localStorage.getItem("theme");
|
|
9
|
+
if (preference) {
|
|
10
|
+
return preference;
|
|
11
|
+
}
|
|
12
|
+
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
13
|
+
return "dark";
|
|
14
|
+
} else {
|
|
15
|
+
return "light";
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function currentTheme() {
|
|
20
|
+
if (htmlTag.classList.contains(darkmode)) {
|
|
21
|
+
return "dark"
|
|
22
|
+
} else {
|
|
23
|
+
return "light"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setTheme(theme) {
|
|
28
|
+
localStorage.setItem("theme", theme);
|
|
29
|
+
if (theme === "dark") {
|
|
30
|
+
htmlTag.classList.remove(lightmode);
|
|
31
|
+
htmlTag.classList.add(darkmode);
|
|
32
|
+
toggle.textContent = '🌘';
|
|
33
|
+
} else {
|
|
34
|
+
htmlTag.classList.remove(darkmode);
|
|
35
|
+
htmlTag.classList.add(lightmode);
|
|
36
|
+
toggle.textContent = '🌞';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setTheme(preferredTheme());
|
|
41
|
+
|
|
42
|
+
toggle.addEventListener("click", function() {
|
|
43
|
+
let current = currentTheme();
|
|
44
|
+
console.log("Current theme: " + current);
|
|
45
|
+
if (current === "dark") {
|
|
46
|
+
setTheme("light");
|
|
47
|
+
} else {
|
|
48
|
+
setTheme("dark");
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const input = document.getElementById('filterInput');
|
|
2
|
+
const inputMessage = document.getElementById('filterMessage');
|
|
3
|
+
const form = document.getElementById('filterForm');
|
|
4
|
+
|
|
5
|
+
function validateInput(inputString) {
|
|
6
|
+
if (!inputString || inputString.trim() === '') {
|
|
7
|
+
return "empty";
|
|
8
|
+
}
|
|
9
|
+
const validationRegex = /^(?:(?:id|cluster|metadata\.[\w\.\=\-]+|locality\.?(?:zone|sub_zone|region))=[a-zA-Z0-9_-]+ ?)*$/;
|
|
10
|
+
return validationRegex.test(inputString);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
14
|
+
const match = document.cookie.match(/(?:^|; )node_expression=([^;]*)/);
|
|
15
|
+
if (match) {
|
|
16
|
+
input.value = match[1];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
input.addEventListener('input', (event) => {
|
|
21
|
+
const result = validateInput(event.target.value);
|
|
22
|
+
if (result === "empty") {
|
|
23
|
+
input.className = "input is-dark";
|
|
24
|
+
inputMessage.className = "help is-dark";
|
|
25
|
+
inputMessage.innerHTML = "";
|
|
26
|
+
} else if (result === true) {
|
|
27
|
+
input.className = "input is-success";
|
|
28
|
+
inputMessage.className = "help is-success";
|
|
29
|
+
inputMessage.innerHTML = "Press enter to apply filter expression";
|
|
30
|
+
} else {
|
|
31
|
+
input.className = "input is-danger";
|
|
32
|
+
inputMessage.className = "help is-danger";
|
|
33
|
+
inputMessage.innerHTML = "The node filter expression may have no effect, or be invalid";
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
form.addEventListener('submit', (event) => {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
const value = input.value.trim();
|
|
40
|
+
document.cookie = `node_expression=${value}; path=/ui/resources/; max-age=31536000`;
|
|
41
|
+
location.reload();
|
|
42
|
+
});
|