Skip to content

Commit 5ef96e6

Browse files
committed
First draft of a converter web extension
1 parent ef83c51 commit 5ef96e6

File tree

8 files changed

+790
-0
lines changed

8 files changed

+790
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/bin
2+
*.wasm

cmd/wasm/main.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package main
2+
3+
import (
4+
"strings"
5+
"syscall/js"
6+
7+
"github.com/K-Phoen/dark/internal/pkg/converter"
8+
"go.uber.org/zap"
9+
)
10+
11+
func dashboardToDarkFunc() js.Func {
12+
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
13+
if len(args) != 1 {
14+
return "invalid nnumber of arguments passed"
15+
}
16+
17+
logger := zap.NewNop()
18+
conv := converter.NewJSON(logger)
19+
20+
inputJSON := args[0].String()
21+
output := &strings.Builder{}
22+
23+
if err := conv.ToK8SManifest(strings.NewReader(inputJSON), output, converter.K8SManifestOptions{
24+
Folder: "folder_name",
25+
Name: "name",
26+
Namespace: "default",
27+
}); err != nil {
28+
logger.Fatal("Could not convert dashboard", zap.Error(err))
29+
}
30+
31+
return output.String()
32+
})
33+
}
34+
35+
func main() {
36+
js.Global().Set("dashboardToDark", dashboardToDarkFunc())
37+
38+
c := make(chan struct{}, 0)
39+
<-c
40+
}

dark-web-extension/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# DARK web extension
2+
3+
Browser extension to convert Grafana dashboards into an equivalent DARK YAML file.
4+
5+
Whenever a Grafana dashboard is viewed, the extension will add an export button to the dashboard's toolbar.
6+
7+
**Note:** only tested with Firefox.
8+
9+
## Building
10+
11+
### Compile the `./cmd/wasm` binary to WebAssembly
12+
13+
The extension re-uses the conversion logic written in Go, thanks to WebAssembly.
14+
15+
A binary specific to this use-case is bundled within the repository, and needs to be compiled for the extension to run:
16+
17+
```sh
18+
GOOS=js GOARCH=wasm go build -o dark-web-extension/dark.wasm ./cmd/wasm
19+
```
20+
21+
### `./dark-web-extension/wasm_exec.js` file
22+
23+
Distributed with Go:
24+
25+
```sh
26+
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./dark-web-extension
27+
```
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Put all the javascript code here, that you want to execute in background.
2+
3+
console.debug("starting background worker");
4+
5+
browser.runtime.onMessage.addListener(handleMessage);
6+
7+
function handleMessage(request, _sender, _sendResponse) {
8+
console.info(`content script sent a message`, request);
9+
10+
if (request.action === 'convert-to-k8s') {
11+
convertToK8s(request.data.model).then(result => {
12+
const blob = new Blob([result], {type: 'text/yaml'});
13+
14+
browser.downloads.download({
15+
url: window.URL.createObjectURL(blob),
16+
filename: 'dark-dashboard.yaml',
17+
conflictAction: 'uniquify',
18+
});
19+
});
20+
}
21+
}
22+
23+
async function convertToK8s(dashboardModel) {
24+
const go = new Go();
25+
26+
return WebAssembly.instantiateStreaming(
27+
fetch(browser.runtime.getURL("dark.wasm")),
28+
go.importObject,
29+
).then(result => {
30+
go.run(result.instance);
31+
32+
return dashboardToDark(dashboardModel);
33+
});
34+
}

dark-web-extension/content_script.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Put all the javascript code here, that you want to execute after page load.
2+
if (isGrafana()) {
3+
console.debug("content_script bootstrap");
4+
5+
// hackish way of giving enough time for grafana to load the dashboard
6+
waitForElm('.page-toolbar').then((elm) => {
7+
console.debug('page-toolbar found, inserting DARK export button');
8+
setupExportButton();
9+
});
10+
}
11+
12+
function setupExportButton() {
13+
var elements = document.getElementsByClassName("page-toolbar");
14+
15+
if (elements.length === 0) {
16+
console.debug("page-toolbar not found");
17+
return;
18+
}
19+
20+
console.debug("appending export button");
21+
const pageToolbarElmt = elements[0];
22+
23+
const lastButton = pageToolbarElmt.childNodes[pageToolbarElmt.childNodes.length - 1];
24+
const darkExportButton = lastButton.cloneNode(true);
25+
darkExportButton.onclick = triggerExport;
26+
darkExportButton.title = 'Export as DARK dashboard';
27+
28+
darkExportButton.childNodes[0].childNodes[0].childNodes[0].innerHTML = `<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
29+
viewBox="0 0 493.525 493.525" style="enable-background:new 0 0 493.525 493.525;" xml:space="preserve">
30+
<g id="XMLID_30_">
31+
<path id="XMLID_32_" d="M430.557,79.556H218.44c21.622,12.688,40.255,29.729,54.859,49.906h157.258
32+
c7.196,0,13.063,5.863,13.063,13.06v238.662c0,7.199-5.866,13.064-13.063,13.064H191.894c-7.198,0-13.062-5.865-13.062-13.064
33+
V222.173c-6.027-3.1-12.33-5.715-18.845-7.732c-3.818,11.764-12.105,21.787-23.508,27.781c-2.39,1.252-4.987,2.014-7.554,2.844
34+
v136.119c0,34.717,28.25,62.971,62.968,62.971h238.663c34.718,0,62.969-28.254,62.969-62.971V142.522
35+
C493.525,107.806,465.275,79.556,430.557,79.556z"/>
36+
<path id="XMLID_31_" d="M129.037,175.989c51.419,1.234,96.388,28.283,122.25,68.865c2.371,3.705,6.434,5.848,10.657,5.848
37+
c1.152,0,2.322-0.162,3.46-0.486c5.377-1.545,9.114-6.418,9.179-12.006c0-0.504,0-1.01,0-1.51
38+
c0-81.148-64.853-147.023-145.527-148.957V64.155c0-5.492-3.038-10.512-7.879-13.078c-2.16-1.139-4.533-1.707-6.889-1.707
39+
c-2.94,0-5.848,0.88-8.35,2.584L5.751,120.526C2.162,122.98,0.018,127.041,0,131.394c-0.017,4.338,2.113,8.418,5.687,10.902
40+
l100.17,69.451c2.518,1.753,5.459,2.631,8.414,2.631c2.355,0,4.696-0.553,6.857-1.676c4.855-2.549,7.909-7.6,7.909-13.092V175.989z
41+
"/>
42+
</g>
43+
</svg>`;
44+
45+
pageToolbarElmt.appendChild(darkExportButton);
46+
}
47+
48+
function triggerExport() {
49+
const dashboardUID = getDashboardUID();
50+
51+
if (!dashboardUID) {
52+
console.warn('could not infer dashboard UID');
53+
return;
54+
}
55+
56+
console.debug("exporting dashboard with UID", dashboardUID);
57+
58+
fetch(`${window.location.origin}/api/dashboards/uid/${dashboardUID}`)
59+
.then(response => response.json())
60+
.then(apiResponse => convertGrafanaDashboard(apiResponse.dashboard))
61+
.catch(error => {
62+
console.error(`Error: ${error}`);
63+
})
64+
}
65+
66+
function getDashboardUID() {
67+
const pagePath = window.location.pathname;
68+
69+
return pagePath.split('/')[2];
70+
}
71+
72+
function convertGrafanaDashboard(model) {
73+
return browser.runtime.sendMessage({
74+
action: "convert-to-k8s",
75+
data: {
76+
model: JSON.stringify(model),
77+
},
78+
});
79+
}
80+
81+
function isGrafana() {
82+
return document.body.classList.contains('app-grafana');
83+
}
84+
85+
function waitForElm(selector) {
86+
return new Promise(resolve => {
87+
if (document.querySelector(selector)) {
88+
return resolve(document.querySelector(selector));
89+
}
90+
91+
const observer = new MutationObserver(mutations => {
92+
if (document.querySelector(selector)) {
93+
resolve(document.querySelector(selector));
94+
observer.disconnect();
95+
}
96+
});
97+
98+
observer.observe(document.body, {
99+
childList: true,
100+
subtree: true
101+
});
102+
});
103+
}

dark-web-extension/icons/icon.png

2.99 KB
Loading

dark-web-extension/manifest.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"manifest_version": 2,
3+
"name": "dark",
4+
"description": "Converts Grafana dashboards into an equivalent DARK YAML file",
5+
"version": "0.0.1",
6+
"icons": {
7+
"64": "icons/icon.png"
8+
},
9+
10+
"permissions": [
11+
"downloads"
12+
],
13+
14+
"background": {
15+
"scripts": [
16+
"wasm_exec.js",
17+
"background_script.js"
18+
]
19+
},
20+
"content_scripts": [
21+
{
22+
"matches": [
23+
"http://*/*",
24+
"https://*/*"
25+
],
26+
"js": [
27+
"content_script.js"
28+
]
29+
}
30+
]
31+
}

0 commit comments

Comments
 (0)