sovereign 1.0.0b127__py3-none-any.whl → 1.0.0b128__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.
Potentially problematic release.
This version of sovereign might be problematic. Click here for more details.
- sovereign/app.py +1 -1
- sovereign/rendering.py +7 -9
- sovereign/server.py +4 -3
- {sovereign-1.0.0b127.dist-info → sovereign-1.0.0b128.dist-info}/METADATA +4 -4
- {sovereign-1.0.0b127.dist-info → sovereign-1.0.0b128.dist-info}/RECORD +7 -14
- {sovereign-1.0.0b127.dist-info → sovereign-1.0.0b128.dist-info}/WHEEL +1 -1
- sovereign/static/darkmode.js +0 -51
- sovereign/static/node_expression.js +0 -42
- sovereign/static/panel.js +0 -76
- sovereign/static/resources.css +0 -246
- sovereign/static/resources.js +0 -642
- sovereign/static/sass/style.scss +0 -33
- sovereign-1.0.0b127.dist-info/LICENSE.txt +0 -13
- {sovereign-1.0.0b127.dist-info → sovereign-1.0.0b128.dist-info}/entry_points.txt +0 -0
sovereign/app.py
CHANGED
|
@@ -101,7 +101,7 @@ def init_app() -> FastAPI:
|
|
|
101
101
|
|
|
102
102
|
@application.get("/static/{filename}", summary="Return a static asset")
|
|
103
103
|
async def static(filename: str) -> Response:
|
|
104
|
-
return FileResponse(get_package_file("sovereign", f"static/{filename}")) # type: ignore[arg-type]
|
|
104
|
+
return FileResponse(get_package_file("sovereign-files", f"static/{filename}")) # type: ignore[arg-type]
|
|
105
105
|
|
|
106
106
|
@application.get(
|
|
107
107
|
"/admin/xds_dump",
|
sovereign/rendering.py
CHANGED
|
@@ -7,8 +7,8 @@ Functions used to render and return discovery responses to Envoy proxies.
|
|
|
7
7
|
The templates are configurable. `todo See ref:Configuration#Templates`
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
import
|
|
11
|
-
from multiprocessing import Process,
|
|
10
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
11
|
+
from multiprocessing import Process, cpu_count
|
|
12
12
|
from typing import Any, Dict, List
|
|
13
13
|
|
|
14
14
|
import yaml
|
|
@@ -30,7 +30,7 @@ from sovereign.schemas import (
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
# limit render jobs to number of cores
|
|
33
|
-
|
|
33
|
+
POOL = ThreadPoolExecutor(max_workers=cpu_count())
|
|
34
34
|
|
|
35
35
|
type_urls = {
|
|
36
36
|
"v2": {
|
|
@@ -59,14 +59,12 @@ class RenderJob(pydantic.BaseModel):
|
|
|
59
59
|
context: dict[str, Any]
|
|
60
60
|
|
|
61
61
|
def spawn(self):
|
|
62
|
-
|
|
63
|
-
t.start()
|
|
62
|
+
POOL.submit(self._run)
|
|
64
63
|
|
|
65
64
|
def _run(self):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
proc.join()
|
|
65
|
+
proc = Process(target=generate, args=[self])
|
|
66
|
+
proc.start()
|
|
67
|
+
proc.join()
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
def generate(job: RenderJob) -> None:
|
sovereign/server.py
CHANGED
|
@@ -6,16 +6,15 @@ from pathlib import Path
|
|
|
6
6
|
import uvicorn
|
|
7
7
|
|
|
8
8
|
from sovereign import application_logger as log
|
|
9
|
-
from sovereign.app import app
|
|
10
|
-
from sovereign.worker import worker as worker_app
|
|
11
9
|
from sovereign.schemas import SovereignAsgiConfig, SupervisordConfig
|
|
12
10
|
|
|
13
|
-
|
|
14
11
|
asgi_config = SovereignAsgiConfig()
|
|
15
12
|
supervisord_config = SupervisordConfig()
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
def web() -> None:
|
|
16
|
+
from sovereign.app import app
|
|
17
|
+
|
|
19
18
|
log.debug("Starting web server")
|
|
20
19
|
uvicorn.run(
|
|
21
20
|
app,
|
|
@@ -30,6 +29,8 @@ def web() -> None:
|
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def worker():
|
|
32
|
+
from sovereign.worker import worker as worker_app
|
|
33
|
+
|
|
33
34
|
log.debug("Starting worker")
|
|
34
35
|
uvicorn.run(
|
|
35
36
|
worker_app,
|
|
@@ -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.0b128
|
|
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
|
|
@@ -49,7 +49,6 @@ Requires-Dist: jmespath (>=1.0.1,<2.0.0)
|
|
|
49
49
|
Requires-Dist: orjson (>=3.9.15,<4.0.0) ; extra == "orjson"
|
|
50
50
|
Requires-Dist: pydantic (>=2.7.2,<3.0.0)
|
|
51
51
|
Requires-Dist: pydantic-settings (<2.6.0)
|
|
52
|
-
Requires-Dist: redis (<=5.0.0)
|
|
53
52
|
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
54
53
|
Requires-Dist: sentry-sdk (>=2.14.0,<3.0.0) ; extra == "sentry"
|
|
55
54
|
Requires-Dist: starlette (>=0.47.2,<0.48.0)
|
|
@@ -60,6 +59,7 @@ Requires-Dist: ujson (>=5.8.0,<6.0.0) ; extra == "ujson"
|
|
|
60
59
|
Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
|
|
61
60
|
Requires-Dist: uvloop (>=0.19.0,<0.20.0)
|
|
62
61
|
Project-URL: Documentation, https://developer.atlassian.com/platform/sovereign/
|
|
62
|
+
Project-URL: Homepage, https://pypi.org/project/sovereign/
|
|
63
63
|
Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
|
|
64
64
|
Description-Content-Type: text/markdown
|
|
65
65
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
sovereign/__init__.py,sha256=m8MVzaMSW4AvAqHvUAsXFdp8Oas5oQ8X7BcVt7Hfcik,1431
|
|
2
|
-
sovereign/app.py,sha256=
|
|
2
|
+
sovereign/app.py,sha256=wEvPbXwhArGrBrn2PrGLlBpah-9GNhAZ4p3pkTNuk2Y,4882
|
|
3
3
|
sovereign/cache.py,sha256=f7d-x5ksRmd_tbSgdl5xKFXmwgUsMWECg98S5DZYc3w,5333
|
|
4
4
|
sovereign/constants.py,sha256=qdWD1lTvkaW5JGF7TmZhfksQHlRAJFVqbG7v6JQA9k8,46
|
|
5
5
|
sovereign/context.py,sha256=aoGJ5k1n8ytCk7jggQ6XTx6Hx_ocy7cEkvGOFS3gzzc,8912
|
|
@@ -15,21 +15,15 @@ sovereign/logging/types.py,sha256=rGqJAEVvgvzHy4aPfvEH6yQ-yblXNkEcWG7G8l9ALEA,28
|
|
|
15
15
|
sovereign/middlewares.py,sha256=6w4JpvtNGvQA4rocQsYQjuu-ckhpKT6gKYA16T-kiqA,3082
|
|
16
16
|
sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
sovereign/modifiers/lib.py,sha256=Cx0VrpTKbSjb3YmHyG4Jy6YEaPlrwpeqNaom3zu1_hw,2885
|
|
18
|
-
sovereign/rendering.py,sha256=
|
|
18
|
+
sovereign/rendering.py,sha256=zMaUMpH0oezFt1hXVuGTUrkGc5aI9W7RQ_SX-8pxfCU,5642
|
|
19
19
|
sovereign/response_class.py,sha256=beMAFV-4L6DwyWzJzy71GkEW4gb7fzH1jd8-Tul13cU,427
|
|
20
20
|
sovereign/schemas.py,sha256=Jf_w2mAX1b31eCAtJuxSZgy9KMVRKntmnEbbCZGZCxQ,38067
|
|
21
|
-
sovereign/server.py,sha256=
|
|
21
|
+
sovereign/server.py,sha256=GAuBj73fpfZH760dDgVZFfI_2aKj3QHFv1JaVm4Eqow,2979
|
|
22
22
|
sovereign/sources/__init__.py,sha256=g9hEpFk8j5i1ApHQpbc9giTyJW41Ppgsqv5P9zGxOJk,78
|
|
23
23
|
sovereign/sources/file.py,sha256=prUThsDCSPNwZaZpkKXhAm-GVRZWbBoGKGU0It4HHXs,690
|
|
24
24
|
sovereign/sources/inline.py,sha256=pP77m7bHjqE3sSoqZthcuw1ARVMf9gooVwbz4B8OAek,1003
|
|
25
25
|
sovereign/sources/lib.py,sha256=0hk_G6mKJrB65WokVZnqF5kdJ3vsQZMNPuJqJO0mBsI,1031
|
|
26
26
|
sovereign/sources/poller.py,sha256=zpNUhQft-NoJbbxO1kCFp6jJSRSkBmf181xodnF_TiI,18469
|
|
27
|
-
sovereign/static/darkmode.js,sha256=3ip-eKGctDvNhN7UgmaHhzls7r5qIY-Jvh2EpefHbQ0,1449
|
|
28
|
-
sovereign/static/node_expression.js,sha256=GKxKTSRc_96IbL3H4L_31ueJFXq4N7scm5R1RNqxP24,1489
|
|
29
|
-
sovereign/static/panel.js,sha256=i5mGExjv-I4Gtt9dQiTyFwPZa8pg5rXeuTeidXNUiTE,2695
|
|
30
|
-
sovereign/static/resources.css,sha256=Rt_ir_FkoI-VIAOqPhk0vILy8kB2egAYbQU26SOs1io,4500
|
|
31
|
-
sovereign/static/resources.js,sha256=-TaXZ6tohyKA1SkX5YwrTcV5M8mOZ68cvEXpvZWznTo,24506
|
|
32
|
-
sovereign/static/sass/style.scss,sha256=LdGXXuHi_tyMc7XhijIOrlIxyfLt827AAs2Z7DYpFpg,990
|
|
33
27
|
sovereign/statistics.py,sha256=QhDB0bs5kZDGjy248AOIv_bzNbz_c2U7xmJ0hoUNOmw,2033
|
|
34
28
|
sovereign/templates/base.html,sha256=MMhhvvClTixKibYfhXm8Ezx6ttu6Sqki44niciCPMO4,2990
|
|
35
29
|
sovereign/templates/err.html,sha256=a3cEzOqyqWOIe3YxfTEjkxbTfxBxq1knD6GwzEFljfs,603
|
|
@@ -62,8 +56,7 @@ sovereign/views/discovery.py,sha256=B_D1ckfbN1dSKBvuFCTyfB79GUUriCADTB53OwZ8D4Q,
|
|
|
62
56
|
sovereign/views/healthchecks.py,sha256=TaXbxkX679jyQ8v5FxtBa2Qa0Z7KuqQ10WgAqfuVGUc,1743
|
|
63
57
|
sovereign/views/interface.py,sha256=o6DaOqoh2M09_SsZrCOxr9rCVxMUZHXRXj4TNq800Ho,6999
|
|
64
58
|
sovereign/worker.py,sha256=Y4le54cZU1Enj8scu1G1sM_KPVPUmZ7vmTuxpd59X2Q,5855
|
|
65
|
-
sovereign-1.0.
|
|
66
|
-
sovereign-1.0.
|
|
67
|
-
sovereign-1.0.
|
|
68
|
-
sovereign-1.0.
|
|
69
|
-
sovereign-1.0.0b127.dist-info/RECORD,,
|
|
59
|
+
sovereign-1.0.0b128.dist-info/METADATA,sha256=Ohj3VOh3CQOwu8Ym_SIT0LMdhwKf-oNLFKlBSzorco0,6336
|
|
60
|
+
sovereign-1.0.0b128.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
61
|
+
sovereign-1.0.0b128.dist-info/entry_points.txt,sha256=VKJdnnN_HNL8xYQMXsFXfFmN6QkdXMEk5S964avxQJI,1404
|
|
62
|
+
sovereign-1.0.0b128.dist-info/RECORD,,
|
sovereign/static/darkmode.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
});
|
sovereign/static/panel.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
-
let currentTabFilter = 'all'; // Track the current tab filter
|
|
3
|
-
|
|
4
|
-
function clearSearch() {
|
|
5
|
-
const searchInput = document.getElementById('searchInput');
|
|
6
|
-
if (searchInput) {
|
|
7
|
-
searchInput.value = '';
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Function to apply both tab and search filters
|
|
12
|
-
function applyFilters() {
|
|
13
|
-
const searchInput = document.getElementById('searchInput');
|
|
14
|
-
const query = searchInput ? searchInput.value.toLowerCase() : '';
|
|
15
|
-
const virtualHosts = document.querySelectorAll('.virtualhost');
|
|
16
|
-
|
|
17
|
-
virtualHosts.forEach(vh => {
|
|
18
|
-
const text = vh.textContent.toLowerCase();
|
|
19
|
-
const category = vh.getAttribute("data-category");
|
|
20
|
-
|
|
21
|
-
// Check if it passes the tab filter
|
|
22
|
-
const passesTabFilter = (currentTabFilter === 'all') || (category === currentTabFilter);
|
|
23
|
-
|
|
24
|
-
// Check if it passes the search filter
|
|
25
|
-
const passesSearchFilter = query === '' || text.includes(query);
|
|
26
|
-
|
|
27
|
-
// Show only if it passes both filters
|
|
28
|
-
if (passesTabFilter && passesSearchFilter) {
|
|
29
|
-
vh.classList.remove('filtered');
|
|
30
|
-
} else {
|
|
31
|
-
vh.classList.add('filtered');
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Function to hide all panels except active
|
|
37
|
-
function updateVisibility() {
|
|
38
|
-
const panelBlocks = document.querySelectorAll('.virtualhost');
|
|
39
|
-
panelBlocks.forEach(block => {
|
|
40
|
-
if (!block.classList.contains('is-active')) {
|
|
41
|
-
block.classList.add('filtered');
|
|
42
|
-
} else {
|
|
43
|
-
block.classList.remove('filtered');
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
updateVisibility();
|
|
48
|
-
|
|
49
|
-
window.filterTabs = function(element, filter) {
|
|
50
|
-
const tabs = document.querySelectorAll('.panel-tabs a');
|
|
51
|
-
tabs.forEach(tab => tab.classList.remove('is-active'));
|
|
52
|
-
element.classList.add('is-active');
|
|
53
|
-
|
|
54
|
-
currentTabFilter = filter; // Update the current tab filter
|
|
55
|
-
|
|
56
|
-
// Clear virtual hosts search input when switching tabs
|
|
57
|
-
const searchInput = document.getElementById('searchInput');
|
|
58
|
-
if (searchInput) {
|
|
59
|
-
searchInput.value = null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
applyFilters();
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const searchInput = document.getElementById('searchInput');
|
|
66
|
-
if (searchInput) {
|
|
67
|
-
searchInput.addEventListener('input', function() {
|
|
68
|
-
applyFilters(); // Apply both filters when searching
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const allTab = document.querySelector('.panel-tabs a.is-active');
|
|
73
|
-
if (allTab) {
|
|
74
|
-
filterTabs(allTab, 'all');
|
|
75
|
-
}
|
|
76
|
-
});
|
sovereign/static/resources.css
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/* Resources page styles */
|
|
2
|
-
#filterInput {
|
|
3
|
-
font-family: monospace;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.filtered {
|
|
7
|
-
display: none;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.tooltip {
|
|
11
|
-
position: relative;
|
|
12
|
-
display: inline-block;
|
|
13
|
-
cursor: help;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.tooltip .tooltip-text {
|
|
17
|
-
visibility: hidden;
|
|
18
|
-
width: 220px;
|
|
19
|
-
background-color: #363636;
|
|
20
|
-
color: #fff;
|
|
21
|
-
text-align: left;
|
|
22
|
-
padding: 0.5rem;
|
|
23
|
-
border-radius: 6px;
|
|
24
|
-
position: absolute;
|
|
25
|
-
z-index: 1;
|
|
26
|
-
top: 125%;
|
|
27
|
-
left: 50%;
|
|
28
|
-
margin-left: -110px;
|
|
29
|
-
opacity: 0;
|
|
30
|
-
transition: opacity 0.3s;
|
|
31
|
-
font-size: 0.875rem;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.tooltip:hover .tooltip-text {
|
|
35
|
-
visibility: visible;
|
|
36
|
-
opacity: 1;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* Side panel styles - using Bulma-compatible approach */
|
|
40
|
-
.side-panel {
|
|
41
|
-
position: fixed;
|
|
42
|
-
top: 0;
|
|
43
|
-
right: -100%;
|
|
44
|
-
width: 95%;
|
|
45
|
-
max-width: 1200px;
|
|
46
|
-
min-width: 400px;
|
|
47
|
-
height: 100vh;
|
|
48
|
-
z-index: 1000;
|
|
49
|
-
transition: right 0.3s ease-in-out;
|
|
50
|
-
overflow-y: auto;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.side-panel.is-active {
|
|
54
|
-
right: 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/* Responsive side panel sizing */
|
|
58
|
-
@media (min-width: 768px) {
|
|
59
|
-
.side-panel { width: 75%; right: -75%; }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@media (min-width: 1024px) {
|
|
63
|
-
.side-panel { width: 65%; right: -65%; }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
@media (min-width: 1400px) {
|
|
67
|
-
.side-panel { width: 55%; right: -55%; }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@media (min-width: 1600px) {
|
|
71
|
-
.side-panel { width: 50%; right: -50%; }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@media (max-width: 480px) {
|
|
75
|
-
.side-panel { width: 95%; min-width: 300px; right: -95%; }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.json-container {
|
|
79
|
-
position: relative;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.json-header {
|
|
83
|
-
padding: 1rem;
|
|
84
|
-
border-bottom: 1px solid #555;
|
|
85
|
-
background-color: #2d2d2d;
|
|
86
|
-
display: flex;
|
|
87
|
-
justify-content: space-between;
|
|
88
|
-
align-items: center;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.json-content {
|
|
92
|
-
background-color: #1e1e1e;
|
|
93
|
-
border: none;
|
|
94
|
-
border-radius: 0;
|
|
95
|
-
padding: 1rem;
|
|
96
|
-
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', 'Menlo', 'Consolas', monospace;
|
|
97
|
-
font-size: 0.875rem;
|
|
98
|
-
line-height: 1.5;
|
|
99
|
-
white-space: pre-wrap;
|
|
100
|
-
word-wrap: break-word;
|
|
101
|
-
max-height: calc(100vh - 250px);
|
|
102
|
-
overflow-y: auto;
|
|
103
|
-
color: #d4d4d4;
|
|
104
|
-
margin: 0;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* JSON Syntax Highlighting - VS Code Dark+ theme colors */
|
|
108
|
-
.json-key {
|
|
109
|
-
color: #9cdcfe;
|
|
110
|
-
font-weight: 400;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.json-string {
|
|
114
|
-
color: #ce9178;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.json-number {
|
|
118
|
-
color: #b5cea8;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.json-boolean {
|
|
122
|
-
color: #569cd6;
|
|
123
|
-
font-weight: 500;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.json-null {
|
|
127
|
-
color: #569cd6;
|
|
128
|
-
font-weight: 500;
|
|
129
|
-
font-style: italic;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.json-punctuation {
|
|
133
|
-
color: #d4d4d4;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* Improve readability */
|
|
137
|
-
.json-content::-webkit-scrollbar {
|
|
138
|
-
width: 8px;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.json-content::-webkit-scrollbar-track {
|
|
142
|
-
background: #2d2d2d;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.json-content::-webkit-scrollbar-thumb {
|
|
146
|
-
background: #555;
|
|
147
|
-
border-radius: 4px;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.json-content::-webkit-scrollbar-thumb:hover {
|
|
151
|
-
background: #666;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/* Collapsible JSON */
|
|
155
|
-
.json-toggle {
|
|
156
|
-
cursor: pointer;
|
|
157
|
-
user-select: none;
|
|
158
|
-
color: #808080;
|
|
159
|
-
margin-right: 0.25rem;
|
|
160
|
-
font-family: monospace;
|
|
161
|
-
font-size: 0.875rem;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.json-toggle:hover {
|
|
165
|
-
color: #fff;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.json-collapsible {
|
|
169
|
-
margin-left: 1rem;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.json-collapsed {
|
|
173
|
-
display: none;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.loading-spinner {
|
|
177
|
-
display: inline-block;
|
|
178
|
-
width: 20px;
|
|
179
|
-
height: 20px;
|
|
180
|
-
border: 3px solid #f3f3f3;
|
|
181
|
-
border-top: 3px solid #433fca;
|
|
182
|
-
border-radius: 50%;
|
|
183
|
-
animation: spin 1s linear infinite;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
@keyframes spin {
|
|
187
|
-
0% { transform: rotate(0deg); }
|
|
188
|
-
100% { transform: rotate(360deg); }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/* Prevent body scroll when side panel is open */
|
|
192
|
-
body.side-panel-open {
|
|
193
|
-
overflow: hidden;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/* Backdrop overlay for side panel */
|
|
197
|
-
.side-panel-backdrop {
|
|
198
|
-
position: fixed;
|
|
199
|
-
top: 0;
|
|
200
|
-
left: 0;
|
|
201
|
-
width: 100%;
|
|
202
|
-
height: 100%;
|
|
203
|
-
background-color: rgba(0, 0, 0, 0.5);
|
|
204
|
-
z-index: 999;
|
|
205
|
-
opacity: 0;
|
|
206
|
-
visibility: hidden;
|
|
207
|
-
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.side-panel-backdrop.is-active {
|
|
211
|
-
opacity: 1;
|
|
212
|
-
visibility: visible;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/* Show backdrop on smaller screens */
|
|
216
|
-
@media (max-width: 1024px) {
|
|
217
|
-
.side-panel-backdrop.is-active {
|
|
218
|
-
opacity: 1;
|
|
219
|
-
visibility: visible;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/* Minimal custom styles - using Bulma classes for most styling */
|
|
224
|
-
.json-chunk-controls {
|
|
225
|
-
background-color: #2d2d2d;
|
|
226
|
-
border-bottom: 1px solid #555;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/* Preferences details styling */
|
|
230
|
-
details summary {
|
|
231
|
-
list-style: none;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
details summary::-webkit-details-marker {
|
|
235
|
-
display: none;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
details summary::before {
|
|
239
|
-
content: "▶";
|
|
240
|
-
margin-right: 0.5rem;
|
|
241
|
-
transition: transform 0.2s;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
details[open] summary::before {
|
|
245
|
-
transform: rotate(90deg);
|
|
246
|
-
}
|
sovereign/static/resources.js
DELETED
|
@@ -1,642 +0,0 @@
|
|
|
1
|
-
/* Resources page JavaScript functionality */
|
|
2
|
-
|
|
3
|
-
// Global variables
|
|
4
|
-
let resourceNames = [];
|
|
5
|
-
let resources = [];
|
|
6
|
-
let filteredResources = [];
|
|
7
|
-
let currentResourceData = null;
|
|
8
|
-
let currentChunkSize = 1000; // lines
|
|
9
|
-
let currentChunkIndex = 0;
|
|
10
|
-
let totalChunks = 0;
|
|
11
|
-
let resourceType = '';
|
|
12
|
-
|
|
13
|
-
const perPage = 10;
|
|
14
|
-
let currentPage = 1;
|
|
15
|
-
|
|
16
|
-
// Initialize the resources functionality
|
|
17
|
-
function initializeResources(resourceNamesArray, resourceTypeString) {
|
|
18
|
-
resourceNames = resourceNamesArray;
|
|
19
|
-
resourceType = resourceTypeString;
|
|
20
|
-
resources = resourceNames.map((name, index) => ({ name: name, index: index }));
|
|
21
|
-
filteredResources = [...resources];
|
|
22
|
-
|
|
23
|
-
// Initialize pagination
|
|
24
|
-
renderPage(currentPage);
|
|
25
|
-
|
|
26
|
-
// Set up event listeners
|
|
27
|
-
setupEventListeners();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Function to set envoy_version cookie and reload page
|
|
31
|
-
function setEnvoyVersion(version) {
|
|
32
|
-
document.cookie = `envoy_version=${version}; path=/ui/resources/; max-age=31536000`;
|
|
33
|
-
window.location.reload();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Set up event listeners
|
|
37
|
-
function setupEventListeners() {
|
|
38
|
-
// Pagination event listeners
|
|
39
|
-
const prevBtn = document.getElementById('prev-btn');
|
|
40
|
-
const nextBtn = document.getElementById('next-btn');
|
|
41
|
-
|
|
42
|
-
if (prevBtn) {
|
|
43
|
-
prevBtn.addEventListener('click', () => {
|
|
44
|
-
if (currentPage > 1) {
|
|
45
|
-
currentPage--;
|
|
46
|
-
renderPage(currentPage);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (nextBtn) {
|
|
52
|
-
nextBtn.addEventListener('click', () => {
|
|
53
|
-
const totalPages = Math.ceil(filteredResources.length / perPage);
|
|
54
|
-
if (currentPage < totalPages) {
|
|
55
|
-
currentPage++;
|
|
56
|
-
renderPage(currentPage);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Close side panel when clicking outside of it
|
|
62
|
-
document.addEventListener('click', function(event) {
|
|
63
|
-
const sidePanel = document.getElementById('sidePanel');
|
|
64
|
-
const backdrop = document.getElementById('sidePanelBackdrop');
|
|
65
|
-
const isClickInsidePanel = sidePanel && sidePanel.contains(event.target);
|
|
66
|
-
const isResourceLink = event.target.closest('.panel-block');
|
|
67
|
-
const isBackdropClick = event.target === backdrop;
|
|
68
|
-
|
|
69
|
-
if ((isBackdropClick || (!isClickInsidePanel && !isResourceLink)) && sidePanel && sidePanel.classList.contains('is-active')) {
|
|
70
|
-
closeSidePanel();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Keyboard shortcuts
|
|
75
|
-
document.addEventListener('keydown', function(event) {
|
|
76
|
-
if (event.key === 'Escape') {
|
|
77
|
-
const sidePanel = document.getElementById('sidePanel');
|
|
78
|
-
if (sidePanel && sidePanel.classList.contains('is-active')) {
|
|
79
|
-
closeSidePanel();
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Side panel functionality
|
|
86
|
-
function openSidePanel() {
|
|
87
|
-
const sidePanel = document.getElementById('sidePanel');
|
|
88
|
-
const backdrop = document.getElementById('sidePanelBackdrop');
|
|
89
|
-
|
|
90
|
-
if (sidePanel) sidePanel.classList.add('is-active');
|
|
91
|
-
if (backdrop) backdrop.classList.add('is-active');
|
|
92
|
-
document.body.classList.add('side-panel-open');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function closeSidePanel() {
|
|
96
|
-
const sidePanel = document.getElementById('sidePanel');
|
|
97
|
-
const backdrop = document.getElementById('sidePanelBackdrop');
|
|
98
|
-
|
|
99
|
-
if (sidePanel) sidePanel.classList.remove('is-active');
|
|
100
|
-
if (backdrop) backdrop.classList.remove('is-active');
|
|
101
|
-
document.body.classList.remove('side-panel-open');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function showLoading() {
|
|
105
|
-
const content = document.getElementById('sidePanelContent');
|
|
106
|
-
if (content) {
|
|
107
|
-
content.innerHTML = `
|
|
108
|
-
<div style="text-align: center; padding: 2rem;">
|
|
109
|
-
<div class="loading-spinner"></div>
|
|
110
|
-
<p style="margin-top: 1rem;">Loading resource data...</p>
|
|
111
|
-
</div>
|
|
112
|
-
`;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function showError(message) {
|
|
117
|
-
const content = document.getElementById('sidePanelContent');
|
|
118
|
-
if (content) {
|
|
119
|
-
content.innerHTML = `
|
|
120
|
-
<div class="notification is-danger">
|
|
121
|
-
<strong>Error:</strong> ${message}
|
|
122
|
-
</div>
|
|
123
|
-
`;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function escapeHtml(text) {
|
|
128
|
-
const div = document.createElement('div');
|
|
129
|
-
div.textContent = text;
|
|
130
|
-
return div.innerHTML;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function syntaxHighlight(json) {
|
|
134
|
-
if (typeof json !== 'string') {
|
|
135
|
-
json = JSON.stringify(json, null, 2);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Escape HTML first
|
|
139
|
-
json = escapeHtml(json);
|
|
140
|
-
|
|
141
|
-
// Apply syntax highlighting
|
|
142
|
-
json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
|
143
|
-
let cls = 'json-number';
|
|
144
|
-
if (/^"/.test(match)) {
|
|
145
|
-
if (/:$/.test(match)) {
|
|
146
|
-
cls = 'json-key';
|
|
147
|
-
// Remove the colon from the match for styling, we'll add it back
|
|
148
|
-
match = match.slice(0, -1);
|
|
149
|
-
return '<span class="' + cls + '">' + match + '</span><span class="json-punctuation">:</span>';
|
|
150
|
-
} else {
|
|
151
|
-
cls = 'json-string';
|
|
152
|
-
}
|
|
153
|
-
} else if (/true|false/.test(match)) {
|
|
154
|
-
cls = 'json-boolean';
|
|
155
|
-
} else if (/null/.test(match)) {
|
|
156
|
-
cls = 'json-null';
|
|
157
|
-
}
|
|
158
|
-
return '<span class="' + cls + '">' + match + '</span>';
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Highlight punctuation (brackets, braces, commas)
|
|
162
|
-
json = json.replace(/([{}[\],])/g, '<span class="json-punctuation">$1</span>');
|
|
163
|
-
|
|
164
|
-
return json;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function makeJsonCollapsible(element) {
|
|
168
|
-
// This function would add collapsible functionality
|
|
169
|
-
// For now, we'll keep it simple and just return the element
|
|
170
|
-
// Future enhancement: add click handlers for { } and [ ] to collapse/expand
|
|
171
|
-
return element;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function copyToClipboard(text) {
|
|
175
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
176
|
-
// Use the modern clipboard API
|
|
177
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
178
|
-
showCopySuccess();
|
|
179
|
-
}).catch(err => {
|
|
180
|
-
console.error('Failed to copy: ', err);
|
|
181
|
-
fallbackCopyTextToClipboard(text);
|
|
182
|
-
});
|
|
183
|
-
} else {
|
|
184
|
-
// Fallback for older browsers
|
|
185
|
-
fallbackCopyTextToClipboard(text);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function fallbackCopyTextToClipboard(text) {
|
|
190
|
-
const textArea = document.createElement("textarea");
|
|
191
|
-
textArea.value = text;
|
|
192
|
-
textArea.style.top = "0";
|
|
193
|
-
textArea.style.left = "0";
|
|
194
|
-
textArea.style.position = "fixed";
|
|
195
|
-
document.body.appendChild(textArea);
|
|
196
|
-
textArea.focus();
|
|
197
|
-
textArea.select();
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
const successful = document.execCommand('copy');
|
|
201
|
-
if (successful) {
|
|
202
|
-
showCopySuccess();
|
|
203
|
-
}
|
|
204
|
-
} catch (err) {
|
|
205
|
-
console.error('Fallback: Oops, unable to copy', err);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
document.body.removeChild(textArea);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function showCopySuccess() {
|
|
212
|
-
const copyBtn = document.querySelector('.copy-button');
|
|
213
|
-
if (copyBtn) {
|
|
214
|
-
const originalText = copyBtn.textContent;
|
|
215
|
-
copyBtn.textContent = 'Copied!';
|
|
216
|
-
copyBtn.style.backgroundColor = '#48c774';
|
|
217
|
-
setTimeout(() => {
|
|
218
|
-
copyBtn.textContent = originalText;
|
|
219
|
-
copyBtn.style.backgroundColor = '#433fca';
|
|
220
|
-
}, 2000);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Helper function to generate preferences section HTML
|
|
225
|
-
function getPreferencesHtml() {
|
|
226
|
-
const currentThreshold = localStorage.getItem('jsonSizeThreshold') || 0.5;
|
|
227
|
-
const autoChunk = localStorage.getItem('autoChunkLargeJson') === 'true';
|
|
228
|
-
|
|
229
|
-
return `
|
|
230
|
-
<details class="has-background-grey-darker">
|
|
231
|
-
<summary class="has-background-grey-dark has-text-white-ter p-3 is-clickable" style="cursor: pointer;">
|
|
232
|
-
<span class="is-size-7">Display Preferences</span>
|
|
233
|
-
</summary>
|
|
234
|
-
<div class="box has-background-grey-dark has-text-white-ter m-0" style="border-radius: 0;">
|
|
235
|
-
<div class="content is-small">
|
|
236
|
-
<div class="field">
|
|
237
|
-
<label class="checkbox has-text-white-ter">
|
|
238
|
-
<input type="checkbox" id="autoChunkPreference" ${autoChunk ? 'checked' : ''}
|
|
239
|
-
onchange="event.stopPropagation(); updateAutoChunkPreference(this.checked)"
|
|
240
|
-
class="mr-2">
|
|
241
|
-
Always use chunked view for large files
|
|
242
|
-
</label>
|
|
243
|
-
</div>
|
|
244
|
-
<div class="field is-grouped">
|
|
245
|
-
<div class="control">
|
|
246
|
-
<label class="label is-small has-text-white-ter">Size threshold:</label>
|
|
247
|
-
</div>
|
|
248
|
-
<div class="control">
|
|
249
|
-
<div class="select is-dark is-small">
|
|
250
|
-
<select id="thresholdSelector" value="${currentThreshold}"
|
|
251
|
-
onchange="event.stopPropagation(); updateThresholdPreference(this.value)">
|
|
252
|
-
<option value="0.25" ${currentThreshold == 0.25 ? 'selected' : ''}>250 KB</option>
|
|
253
|
-
<option value="0.5" ${currentThreshold == 0.5 ? 'selected' : ''}>500 KB</option>
|
|
254
|
-
<option value="1" ${currentThreshold == 1 ? 'selected' : ''}>1 MB</option>
|
|
255
|
-
<option value="2" ${currentThreshold == 2 ? 'selected' : ''}>2 MB</option>
|
|
256
|
-
<option value="5" ${currentThreshold == 5 ? 'selected' : ''}>5 MB</option>
|
|
257
|
-
</select>
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
<div class="field is-grouped">
|
|
262
|
-
<div class="control">
|
|
263
|
-
<button class="button is-link is-small" onclick="event.stopPropagation(); showChunkedJson()">
|
|
264
|
-
Switch to Chunked View
|
|
265
|
-
</button>
|
|
266
|
-
</div>
|
|
267
|
-
<div class="control">
|
|
268
|
-
<button class="button is-success is-small" onclick="event.stopPropagation(); showFullJson()">
|
|
269
|
-
Switch to Full View
|
|
270
|
-
</button>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
</details>
|
|
276
|
-
`;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Helper functions to update preferences
|
|
280
|
-
function updateAutoChunkPreference(checked) {
|
|
281
|
-
localStorage.setItem('autoChunkLargeJson', checked.toString());
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function updateThresholdPreference(threshold) {
|
|
285
|
-
localStorage.setItem('jsonSizeThreshold', threshold);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function loadResourceInSidePanel(resourceType, resourceName) {
|
|
289
|
-
const titleElement = document.getElementById('sidePanelTitle');
|
|
290
|
-
if (titleElement) {
|
|
291
|
-
titleElement.textContent = `${resourceType}: ${resourceName}`;
|
|
292
|
-
}
|
|
293
|
-
openSidePanel();
|
|
294
|
-
showLoading();
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
// Get current URL parameters to maintain context
|
|
298
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
299
|
-
const region = urlParams.get('region') || '';
|
|
300
|
-
const apiVersion = urlParams.get('api_version') || 'v2';
|
|
301
|
-
|
|
302
|
-
// Build the fetch URL with current parameters
|
|
303
|
-
let fetchUrl = `/ui/resources/${resourceType}/${encodeURIComponent(resourceName)}`;
|
|
304
|
-
const params = new URLSearchParams();
|
|
305
|
-
if (region) params.append('region', region);
|
|
306
|
-
params.append('api_version', apiVersion);
|
|
307
|
-
|
|
308
|
-
if (params.toString()) {
|
|
309
|
-
fetchUrl += '?' + params.toString();
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const response = await fetch(fetchUrl, {
|
|
313
|
-
credentials: 'same-origin' // Include cookies in the request
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
if (!response.ok) {
|
|
317
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const data = await response.json();
|
|
321
|
-
const jsonString = JSON.stringify(data, null, 2);
|
|
322
|
-
const sizeKB = (new Blob([jsonString]).size / 1024).toFixed(1);
|
|
323
|
-
const sizeMB = sizeKB / 1024;
|
|
324
|
-
|
|
325
|
-
// Store the data globally
|
|
326
|
-
currentResourceData = {
|
|
327
|
-
data: data,
|
|
328
|
-
jsonString: jsonString,
|
|
329
|
-
sizeKB: sizeKB,
|
|
330
|
-
sizeMB: sizeMB
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
// Check if the JSON is large and show warning/chunked view
|
|
334
|
-
// Use a lower threshold (500KB) to be more conservative about browser performance
|
|
335
|
-
const threshold = localStorage.getItem('jsonSizeThreshold') || 0.5; // MB
|
|
336
|
-
if (sizeMB > threshold) {
|
|
337
|
-
// Check user preference for auto-chunking
|
|
338
|
-
const autoChunk = localStorage.getItem('autoChunkLargeJson') === 'true';
|
|
339
|
-
if (autoChunk) {
|
|
340
|
-
showChunkedJson();
|
|
341
|
-
} else {
|
|
342
|
-
showLargeJsonWarning(resourceType, resourceName);
|
|
343
|
-
}
|
|
344
|
-
} else {
|
|
345
|
-
showFullJson();
|
|
346
|
-
}
|
|
347
|
-
} catch (error) {
|
|
348
|
-
console.error('Error loading resource:', error);
|
|
349
|
-
showError(error.message);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function showLargeJsonWarning(resourceType, resourceName) {
|
|
354
|
-
const sizeKB = currentResourceData.sizeKB;
|
|
355
|
-
const sizeMB = currentResourceData.sizeMB;
|
|
356
|
-
|
|
357
|
-
const content = document.getElementById('sidePanelContent');
|
|
358
|
-
if (content) {
|
|
359
|
-
content.innerHTML = `
|
|
360
|
-
<div class="notification is-warning">
|
|
361
|
-
<p>
|
|
362
|
-
<strong>Large JSON detected (${sizeKB} KB / ${sizeMB.toFixed(1)} MB)</strong><br>
|
|
363
|
-
Loading this much data may slow down your browser. <br>
|
|
364
|
-
</p>
|
|
365
|
-
|
|
366
|
-
Choose an option:
|
|
367
|
-
<button class="button is-dark is-small" onclick="event.stopPropagation(); showChunkedJson()">
|
|
368
|
-
View in Chunks
|
|
369
|
-
</button>
|
|
370
|
-
<button class="button is-dark is-small" onclick="event.stopPropagation(); showFullJson()">
|
|
371
|
-
Load Full JSON
|
|
372
|
-
</button>
|
|
373
|
-
</div>
|
|
374
|
-
`;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function showChunkedJson() {
|
|
379
|
-
const lines = currentResourceData.jsonString.split('\n');
|
|
380
|
-
totalChunks = Math.ceil(lines.length / currentChunkSize);
|
|
381
|
-
currentChunkIndex = 0;
|
|
382
|
-
|
|
383
|
-
renderChunkedJson();
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function showFullJson() {
|
|
387
|
-
// Store the JSON string globally for copying
|
|
388
|
-
window.currentJsonData = currentResourceData.jsonString;
|
|
389
|
-
|
|
390
|
-
const content = document.getElementById('sidePanelContent');
|
|
391
|
-
if (content) {
|
|
392
|
-
content.innerHTML = `
|
|
393
|
-
<div class="json-container">
|
|
394
|
-
<div class="json-header">
|
|
395
|
-
<span style="color: #d4d4d4; font-size: 0.875rem;">JSON Response (${currentResourceData.sizeKB} KB) - Full View</span>
|
|
396
|
-
<button class="button is-primary is-small" onclick="event.stopPropagation(); copyToClipboard(window.currentJsonData)">
|
|
397
|
-
Copy JSON
|
|
398
|
-
</button>
|
|
399
|
-
</div>
|
|
400
|
-
${getPreferencesHtml()}
|
|
401
|
-
<div class="json-content">${syntaxHighlight(currentResourceData.data)}</div>
|
|
402
|
-
</div>
|
|
403
|
-
`;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function renderChunkedJson() {
|
|
408
|
-
const lines = currentResourceData.jsonString.split('\n');
|
|
409
|
-
const startLine = currentChunkIndex * currentChunkSize;
|
|
410
|
-
const endLine = Math.min(startLine + currentChunkSize, lines.length);
|
|
411
|
-
const chunkLines = lines.slice(startLine, endLine);
|
|
412
|
-
const chunkText = chunkLines.join('\n');
|
|
413
|
-
|
|
414
|
-
// Store current chunk for copying
|
|
415
|
-
window.currentJsonData = currentResourceData.jsonString; // Always allow copying full JSON
|
|
416
|
-
window.currentChunkData = chunkText;
|
|
417
|
-
|
|
418
|
-
const content = document.getElementById('sidePanelContent');
|
|
419
|
-
if (content) {
|
|
420
|
-
content.innerHTML = `
|
|
421
|
-
<div class="json-container">
|
|
422
|
-
<div class="json-header">
|
|
423
|
-
<span style="color: #d4d4d4; font-size: 0.875rem;">JSON Response (${currentResourceData.sizeKB} KB) - Chunked View</span>
|
|
424
|
-
<button class="button is-primary is-small" onclick="event.stopPropagation(); copyToClipboard(window.currentJsonData)">
|
|
425
|
-
Copy Full JSON
|
|
426
|
-
</button>
|
|
427
|
-
</div>
|
|
428
|
-
${getPreferencesHtml()}
|
|
429
|
-
<div class="json-chunk-controls p-4">
|
|
430
|
-
<div class="field is-grouped is-grouped-multiline">
|
|
431
|
-
<div class="control">
|
|
432
|
-
<label class="label is-small has-text-white">Lines per chunk:</label>
|
|
433
|
-
<div class="select is-dark is-small">
|
|
434
|
-
<select onchange="event.stopPropagation(); changeChunkSize(this.value)">
|
|
435
|
-
<option value="50" ${currentChunkSize === 50 ? 'selected' : ''}>50</option>
|
|
436
|
-
<option value="100" ${currentChunkSize === 100 ? 'selected' : ''}>100</option>
|
|
437
|
-
<option value="250" ${currentChunkSize === 250 ? 'selected' : ''}>250</option>
|
|
438
|
-
<option value="500" ${currentChunkSize === 500 ? 'selected' : ''}>500</option>
|
|
439
|
-
<option value="1000" ${currentChunkSize === 1000 ? 'selected' : ''}>1000</option>
|
|
440
|
-
<option value="2000" ${currentChunkSize === 2000 ? 'selected' : ''}>2000</option>
|
|
441
|
-
<option value="5000" ${currentChunkSize === 5000 ? 'selected' : ''}>5000</option>
|
|
442
|
-
</select>
|
|
443
|
-
</div>
|
|
444
|
-
</div>
|
|
445
|
-
<div class="control">
|
|
446
|
-
<div class="buttons">
|
|
447
|
-
<button class="button is-primary is-small" onclick="event.stopPropagation(); previousChunk()" ${currentChunkIndex === 0 ? 'disabled' : ''}>
|
|
448
|
-
← Previous
|
|
449
|
-
</button>
|
|
450
|
-
<button class="button is-primary is-small" onclick="event.stopPropagation(); nextChunk()" ${currentChunkIndex >= totalChunks - 1 ? 'disabled' : ''}>
|
|
451
|
-
Next →
|
|
452
|
-
</button>
|
|
453
|
-
<button class="button is-info is-small" onclick="event.stopPropagation(); copyToClipboard(window.currentChunkData)">
|
|
454
|
-
Copy Chunk
|
|
455
|
-
</button>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
</div>
|
|
460
|
-
<progress class="progress is-primary is-small" value="${endLine}" max="${lines.length}">
|
|
461
|
-
Chunk ${currentChunkIndex + 1} of ${totalChunks}
|
|
462
|
-
</progress>
|
|
463
|
-
<div class="json-content">${syntaxHighlight(chunkText)}</div>
|
|
464
|
-
</div>
|
|
465
|
-
`;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function changeChunkSize(newSize) {
|
|
470
|
-
currentChunkSize = parseInt(newSize);
|
|
471
|
-
const lines = currentResourceData.jsonString.split('\n');
|
|
472
|
-
totalChunks = Math.ceil(lines.length / currentChunkSize);
|
|
473
|
-
|
|
474
|
-
// Adjust current chunk index to stay roughly in the same area
|
|
475
|
-
const currentLine = currentChunkIndex * currentChunkSize;
|
|
476
|
-
currentChunkIndex = Math.floor(currentLine / currentChunkSize);
|
|
477
|
-
currentChunkIndex = Math.min(currentChunkIndex, totalChunks - 1);
|
|
478
|
-
|
|
479
|
-
renderChunkedJson();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function previousChunk() {
|
|
483
|
-
if (currentChunkIndex > 0) {
|
|
484
|
-
currentChunkIndex--;
|
|
485
|
-
renderChunkedJson();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function nextChunk() {
|
|
490
|
-
if (currentChunkIndex < totalChunks - 1) {
|
|
491
|
-
currentChunkIndex++;
|
|
492
|
-
renderChunkedJson();
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
async function loadVirtualHostInSidePanel(routeConfiguration, virtualHost) {
|
|
497
|
-
const titleElement = document.getElementById('sidePanelTitle');
|
|
498
|
-
if (titleElement) {
|
|
499
|
-
titleElement.textContent = `Virtual Host: ${virtualHost}`;
|
|
500
|
-
}
|
|
501
|
-
openSidePanel();
|
|
502
|
-
showLoading();
|
|
503
|
-
|
|
504
|
-
try {
|
|
505
|
-
// Get current URL parameters to maintain context
|
|
506
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
507
|
-
const region = urlParams.get('region') || '';
|
|
508
|
-
const apiVersion = urlParams.get('api_version') || 'v2';
|
|
509
|
-
|
|
510
|
-
// Build the fetch URL with current parameters
|
|
511
|
-
let fetchUrl = `/ui/resources/routes/${encodeURIComponent(routeConfiguration)}/${encodeURIComponent(virtualHost)}`;
|
|
512
|
-
const params = new URLSearchParams();
|
|
513
|
-
if (region) params.append('region', region);
|
|
514
|
-
params.append('api_version', apiVersion);
|
|
515
|
-
|
|
516
|
-
if (params.toString()) {
|
|
517
|
-
fetchUrl += '?' + params.toString();
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const response = await fetch(fetchUrl, {
|
|
521
|
-
credentials: 'same-origin' // Include cookies in the request
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
if (!response.ok) {
|
|
525
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const data = await response.json();
|
|
529
|
-
const jsonString = JSON.stringify(data, null, 2);
|
|
530
|
-
const sizeKB = (new Blob([jsonString]).size / 1024).toFixed(1);
|
|
531
|
-
const sizeMB = sizeKB / 1024;
|
|
532
|
-
|
|
533
|
-
// Store the data globally
|
|
534
|
-
currentResourceData = {
|
|
535
|
-
data: data,
|
|
536
|
-
jsonString: jsonString,
|
|
537
|
-
sizeKB: sizeKB,
|
|
538
|
-
sizeMB: sizeMB
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
// Check if the JSON is large and show warning/chunked view
|
|
542
|
-
const threshold = localStorage.getItem('jsonSizeThreshold') || 0.5; // MB
|
|
543
|
-
if (sizeMB > threshold) {
|
|
544
|
-
// Check user preference for auto-chunking
|
|
545
|
-
const autoChunk = localStorage.getItem('autoChunkLargeJson') === 'true';
|
|
546
|
-
if (autoChunk) {
|
|
547
|
-
showChunkedJson();
|
|
548
|
-
} else {
|
|
549
|
-
showLargeJsonWarning('Virtual Host', virtualHost);
|
|
550
|
-
}
|
|
551
|
-
} else {
|
|
552
|
-
showFullJson();
|
|
553
|
-
}
|
|
554
|
-
} catch (error) {
|
|
555
|
-
console.error('Error loading virtual host:', error);
|
|
556
|
-
showError(error.message);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function filter_results(id) {
|
|
561
|
-
const input = document.getElementById(id);
|
|
562
|
-
if (!input) return;
|
|
563
|
-
|
|
564
|
-
const filter = input.value.toLowerCase();
|
|
565
|
-
|
|
566
|
-
filteredResources = resources.filter((res) => {
|
|
567
|
-
const name = res.name || "";
|
|
568
|
-
return name.toLowerCase().includes(filter);
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
currentPage = 1;
|
|
572
|
-
renderPage(currentPage);
|
|
573
|
-
|
|
574
|
-
// Update the resource count display
|
|
575
|
-
const countElement = document.getElementById('resource-count');
|
|
576
|
-
if (countElement) {
|
|
577
|
-
const count = filteredResources.length;
|
|
578
|
-
const plural = count === 1 ? 'resource' : 'resources';
|
|
579
|
-
countElement.textContent = `${count} ${plural}`;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function renderPage(page) {
|
|
584
|
-
const container = document.getElementById('resource-container');
|
|
585
|
-
if (!container) return;
|
|
586
|
-
|
|
587
|
-
container.innerHTML = '';
|
|
588
|
-
|
|
589
|
-
const start = (page - 1) * perPage;
|
|
590
|
-
const end = start + perPage;
|
|
591
|
-
const pageItems = filteredResources.slice(start, end);
|
|
592
|
-
|
|
593
|
-
for (const resource of pageItems) {
|
|
594
|
-
const name = resource.name;
|
|
595
|
-
const item = document.createElement('a');
|
|
596
|
-
item.className = 'panel-block has-text-weight-medium';
|
|
597
|
-
item.href = '#';
|
|
598
|
-
item.onclick = (e) => {
|
|
599
|
-
e.preventDefault();
|
|
600
|
-
loadResourceInSidePanel(resourceType, name);
|
|
601
|
-
};
|
|
602
|
-
item.innerHTML = `
|
|
603
|
-
<span class="panel-icon">
|
|
604
|
-
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
|
605
|
-
</span>
|
|
606
|
-
${name}
|
|
607
|
-
`;
|
|
608
|
-
container.appendChild(item);
|
|
609
|
-
}
|
|
610
|
-
renderPaginationControls();
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function renderPaginationControls() {
|
|
614
|
-
const totalPages = Math.ceil(filteredResources.length / perPage);
|
|
615
|
-
const pageList = document.getElementById('page-numbers');
|
|
616
|
-
if (!pageList) return;
|
|
617
|
-
|
|
618
|
-
pageList.innerHTML = '';
|
|
619
|
-
|
|
620
|
-
for (let i = 1; i <= totalPages; i++) {
|
|
621
|
-
const li = document.createElement('li');
|
|
622
|
-
const a = document.createElement('a');
|
|
623
|
-
a.className = 'pagination-link';
|
|
624
|
-
if (i === currentPage) {
|
|
625
|
-
a.classList.add('has-background-grey-lighter');
|
|
626
|
-
a.classList.add('has-background-black-bis');
|
|
627
|
-
}
|
|
628
|
-
a.textContent = i;
|
|
629
|
-
a.addEventListener('click', () => {
|
|
630
|
-
currentPage = i;
|
|
631
|
-
renderPage(currentPage);
|
|
632
|
-
});
|
|
633
|
-
li.appendChild(a);
|
|
634
|
-
pageList.appendChild(li);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const prevBtn = document.getElementById('prev-btn');
|
|
638
|
-
const nextBtn = document.getElementById('next-btn');
|
|
639
|
-
|
|
640
|
-
if (prevBtn) prevBtn.disabled = currentPage === 1;
|
|
641
|
-
if (nextBtn) nextBtn.disabled = currentPage === totalPages;
|
|
642
|
-
}
|
sovereign/static/sass/style.scss
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
@use "bulma/sass/utilities" with (
|
|
2
|
-
$family-primary: '"Nunito", sans-serif',
|
|
3
|
-
$primary: #433fca,
|
|
4
|
-
$link: #6c90ec,
|
|
5
|
-
);
|
|
6
|
-
|
|
7
|
-
// Update Bulma's global variables
|
|
8
|
-
$family-sans-serif: "Nunito", sans-serif;
|
|
9
|
-
|
|
10
|
-
// Force the navbar menu to not collapse
|
|
11
|
-
$desktop: 1px;
|
|
12
|
-
$body-background-color: rgb(250, 250, 250);
|
|
13
|
-
|
|
14
|
-
// Import only what you need from Bulma
|
|
15
|
-
@forward "bulma/sass/base";
|
|
16
|
-
@forward "bulma/sass/elements/tag";
|
|
17
|
-
@forward "bulma/sass/elements/notification";
|
|
18
|
-
@forward "bulma/sass/elements/button";
|
|
19
|
-
@forward "bulma/sass/elements/content";
|
|
20
|
-
@forward "bulma/sass/elements/title";
|
|
21
|
-
@forward "bulma/sass/components/navbar";
|
|
22
|
-
@forward "bulma/sass/components/panel";
|
|
23
|
-
@forward "bulma/sass/components/pagination";
|
|
24
|
-
@forward "bulma/sass/components/dropdown";
|
|
25
|
-
@forward "bulma/sass/grid/columns";
|
|
26
|
-
@forward "bulma/sass/form";
|
|
27
|
-
@forward "bulma/sass/helpers";
|
|
28
|
-
@forward "bulma/sass/utilities";
|
|
29
|
-
|
|
30
|
-
@forward "bulma/sass/themes";
|
|
31
|
-
|
|
32
|
-
// Import a Google Font
|
|
33
|
-
@import url('https://fonts.googleapis.com/css?family=Nunito:400,700');
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Copyright @ 2018 Atlassian Pty Ltd
|
|
2
|
-
|
|
3
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License.
|
|
5
|
-
You may obtain a copy of the License at
|
|
6
|
-
|
|
7
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
|
|
9
|
-
Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
See the License for the specific language governing permissions and
|
|
13
|
-
limitations under the License.
|
|
File without changes
|