signalk-grafana 0.1.1
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.
- package/.prettierignore +2 -0
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/config/schema.d.ts +13 -0
- package/dist/config/schema.js +50 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +322 -0
- package/dist/index.js.map +1 -0
- package/dist/provisioning.d.ts +2 -0
- package/dist/provisioning.js +72 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/test/provisioning.test.d.ts +1 -0
- package/dist/test/provisioning.test.js +91 -0
- package/dist/test/provisioning.test.js.map +1 -0
- package/package.json +61 -0
- package/public/540.js +2 -0
- package/public/540.js.LICENSE.txt +9 -0
- package/public/805.js +1 -0
- package/public/main.js +1 -0
- package/public/remoteEntry.js +1 -0
- package/webpack.config.js +43 -0
package/.prettierignore
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dirk Wahrheit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# signalk-grafana
|
|
2
|
+
|
|
3
|
+
Managed Grafana with auto-provisioned QuestDB dashboards for Signal K.
|
|
4
|
+
|
|
5
|
+
Runs Grafana in a container (via [signalk-container](https://github.com/dirkwa/signalk-container)), automatically connects it to [signalk-questdb](https://github.com/dirkwa/signalk-questdb) via a shared container network, and provisions pre-built marine dashboards.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Zero-config Grafana** -- container managed automatically, no manual setup
|
|
10
|
+
- **Auto-provisioned QuestDB datasource** -- connects via container DNS, no 0.0.0.0 binding needed
|
|
11
|
+
- **Shared container network** -- Grafana and QuestDB communicate on a private Podman/Docker network
|
|
12
|
+
- **Pre-built dashboards** -- navigation, electrical, engine, environment
|
|
13
|
+
- **Anonymous access** -- view dashboards without login (configurable)
|
|
14
|
+
- **Config panel** -- Grafana status, open link, settings, all in Admin UI
|
|
15
|
+
- **Table auto-discovery** -- QuestDB supports `information_schema`, Grafana query builder works
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
1. Plugin creates a Podman/Docker network (`sk-network`)
|
|
20
|
+
2. Connects the existing QuestDB container to the network
|
|
21
|
+
3. Generates Grafana provisioning files (datasource + dashboards)
|
|
22
|
+
4. Starts Grafana container on the same network
|
|
23
|
+
5. Grafana connects to QuestDB via `sk-signalk-questdb:8812` (container DNS)
|
|
24
|
+
|
|
25
|
+
No ports exposed to the internet. Grafana UI is accessible on the host at the configured port (default `3001`).
|
|
26
|
+
|
|
27
|
+
## Pre-built Dashboards
|
|
28
|
+
|
|
29
|
+
| Dashboard | Panels |
|
|
30
|
+
| --------------- | ------------------------------------------------- |
|
|
31
|
+
| **Navigation** | SOG (kn), COG, depth, heading |
|
|
32
|
+
| **Electrical** | Battery voltage/current, AC power/voltage |
|
|
33
|
+
| **Engine** | RPM, coolant temp, oil pressure, fuel level |
|
|
34
|
+
| **Environment** | Wind speed/angle, barometric pressure, water temp |
|
|
35
|
+
|
|
36
|
+
All queries convert Signal K units (radians, Kelvin, m/s, Pascals) to display units (degrees, Celsius, knots, hPa).
|
|
37
|
+
|
|
38
|
+
Dashboards are editable in Grafana -- customize panels, add new queries, save changes. The provisioned versions are templates to get started.
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
| Setting | Default | Description |
|
|
43
|
+
| ----------------- | ----------------- | ----------------------------------------- |
|
|
44
|
+
| Grafana port | `3001` | Host port for Grafana UI |
|
|
45
|
+
| Image version | `latest` | Grafana Docker image tag |
|
|
46
|
+
| Admin password | `admin` | Initial admin password (set on first run) |
|
|
47
|
+
| Anonymous access | `true` | Allow viewing without login |
|
|
48
|
+
| QuestDB container | `signalk-questdb` | Container name (without sk- prefix) |
|
|
49
|
+
| PostgreSQL port | `8812` | QuestDB PG wire port |
|
|
50
|
+
| Network name | `sk-network` | Shared container network name |
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- Node.js >= 22
|
|
55
|
+
- [signalk-container](https://github.com/dirkwa/signalk-container) plugin
|
|
56
|
+
- [signalk-questdb](https://github.com/dirkwa/signalk-questdb) plugin (for data)
|
|
57
|
+
- Signal K server
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Static } from "@sinclair/typebox";
|
|
2
|
+
export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
|
|
3
|
+
grafanaPort: import("@sinclair/typebox").TNumber;
|
|
4
|
+
grafanaVersion: import("@sinclair/typebox").TString;
|
|
5
|
+
adminPassword: import("@sinclair/typebox").TString;
|
|
6
|
+
anonymousAccess: import("@sinclair/typebox").TBoolean;
|
|
7
|
+
questdbContainerName: import("@sinclair/typebox").TString;
|
|
8
|
+
questdbPgPort: import("@sinclair/typebox").TNumber;
|
|
9
|
+
networkName: import("@sinclair/typebox").TString;
|
|
10
|
+
signalkUrl: import("@sinclair/typebox").TString;
|
|
11
|
+
bindToAllInterfaces: import("@sinclair/typebox").TBoolean;
|
|
12
|
+
}>;
|
|
13
|
+
export type Config = Static<typeof ConfigSchema>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigSchema = void 0;
|
|
4
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
5
|
+
exports.ConfigSchema = typebox_1.Type.Object({
|
|
6
|
+
grafanaPort: typebox_1.Type.Number({
|
|
7
|
+
default: 3001,
|
|
8
|
+
title: "Grafana port",
|
|
9
|
+
description: "Host port for the Grafana UI (avoid 3000 if Signal K uses it)",
|
|
10
|
+
}),
|
|
11
|
+
grafanaVersion: typebox_1.Type.String({
|
|
12
|
+
default: "latest",
|
|
13
|
+
title: "Grafana image version",
|
|
14
|
+
}),
|
|
15
|
+
adminPassword: typebox_1.Type.String({
|
|
16
|
+
default: "admin",
|
|
17
|
+
title: "Admin password",
|
|
18
|
+
description: "Initial Grafana admin password (set on first run)",
|
|
19
|
+
}),
|
|
20
|
+
anonymousAccess: typebox_1.Type.Boolean({
|
|
21
|
+
default: true,
|
|
22
|
+
title: "Anonymous access",
|
|
23
|
+
description: "Allow viewing dashboards without login",
|
|
24
|
+
}),
|
|
25
|
+
questdbContainerName: typebox_1.Type.String({
|
|
26
|
+
default: "signalk-questdb",
|
|
27
|
+
title: "QuestDB container name",
|
|
28
|
+
description: "Container name used by signalk-questdb (without sk- prefix)",
|
|
29
|
+
}),
|
|
30
|
+
questdbPgPort: typebox_1.Type.Number({
|
|
31
|
+
default: 8812,
|
|
32
|
+
title: "QuestDB PostgreSQL port",
|
|
33
|
+
}),
|
|
34
|
+
networkName: typebox_1.Type.String({
|
|
35
|
+
default: "sk-network",
|
|
36
|
+
title: "Container network name",
|
|
37
|
+
description: "Shared Podman/Docker network for Grafana to reach QuestDB",
|
|
38
|
+
}),
|
|
39
|
+
signalkUrl: typebox_1.Type.String({
|
|
40
|
+
default: "",
|
|
41
|
+
title: "Signal K server URL override",
|
|
42
|
+
description: "Auto-detected from PORT env var. Only set to override (e.g. http://192.168.0.122:3000).",
|
|
43
|
+
}),
|
|
44
|
+
bindToAllInterfaces: typebox_1.Type.Boolean({
|
|
45
|
+
default: false,
|
|
46
|
+
title: "Bind to 0.0.0.0",
|
|
47
|
+
description: "Caution! This can expose Grafana to the internet. Only enable if you need remote access.",
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":";;;AAAA,+CAAiD;AAEpC,QAAA,YAAY,GAAG,cAAI,CAAC,MAAM,CAAC;IACtC,WAAW,EAAE,cAAI,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,+DAA+D;KAClE,CAAC;IACF,cAAc,EAAE,cAAI,CAAC,MAAM,CAAC;QAC1B,OAAO,EAAE,QAAQ;QACjB,KAAK,EAAE,uBAAuB;KAC/B,CAAC;IACF,aAAa,EAAE,cAAI,CAAC,MAAM,CAAC;QACzB,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,mDAAmD;KACjE,CAAC;IACF,eAAe,EAAE,cAAI,CAAC,OAAO,CAAC;QAC5B,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,wCAAwC;KACtD,CAAC;IACF,oBAAoB,EAAE,cAAI,CAAC,MAAM,CAAC;QAChC,OAAO,EAAE,iBAAiB;QAC1B,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,6DAA6D;KAC3E,CAAC;IACF,aAAa,EAAE,cAAI,CAAC,MAAM,CAAC;QACzB,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,yBAAyB;KACjC,CAAC;IACF,WAAW,EAAE,cAAI,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,YAAY;QACrB,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,2DAA2D;KACzE,CAAC;IACF,UAAU,EAAE,cAAI,CAAC,MAAM,CAAC;QACtB,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,8BAA8B;QACrC,WAAW,EACT,yFAAyF;KAC5F,CAAC;IACF,mBAAmB,EAAE,cAAI,CAAC,OAAO,CAAC;QAChC,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,0FAA0F;KAC7F,CAAC;CACH,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs_1 = require("fs");
|
|
4
|
+
const schema_1 = require("./config/schema");
|
|
5
|
+
const provisioning_1 = require("./provisioning");
|
|
6
|
+
module.exports = (app) => {
|
|
7
|
+
let currentConfig = null;
|
|
8
|
+
async function asyncStart(config) {
|
|
9
|
+
currentConfig = config;
|
|
10
|
+
const dataDir = app.getDataDirPath();
|
|
11
|
+
let containers;
|
|
12
|
+
const deadline = Date.now() + 30000;
|
|
13
|
+
while (Date.now() < deadline) {
|
|
14
|
+
containers = globalThis.__signalk_containerManager;
|
|
15
|
+
if (containers && containers.getRuntime())
|
|
16
|
+
break;
|
|
17
|
+
app.setPluginStatus("Waiting for container runtime...");
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
19
|
+
}
|
|
20
|
+
if (!containers || !containers.getRuntime()) {
|
|
21
|
+
app.setPluginError("signalk-container plugin required. Install it and ensure a container runtime is available.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
app.setPluginStatus("Ensuring container network...");
|
|
25
|
+
await containers.ensureNetwork(config.networkName);
|
|
26
|
+
app.setPluginStatus("Generating Grafana provisioning...");
|
|
27
|
+
(0, provisioning_1.generateProvisioning)(dataDir, config);
|
|
28
|
+
const bind = config.bindToAllInterfaces ? "0.0.0.0" : "127.0.0.1";
|
|
29
|
+
const containerConfig = {
|
|
30
|
+
image: "grafana/grafana",
|
|
31
|
+
tag: config.grafanaVersion ?? "latest",
|
|
32
|
+
ports: {
|
|
33
|
+
"3000/tcp": `${bind}:${config.grafanaPort}`,
|
|
34
|
+
},
|
|
35
|
+
networkMode: config.networkName,
|
|
36
|
+
volumes: {
|
|
37
|
+
"/etc/grafana/provisioning": `${dataDir}/provisioning`,
|
|
38
|
+
"/var/lib/grafana/dashboards": `${dataDir}/dashboards`,
|
|
39
|
+
"/var/lib/grafana": `${dataDir}/grafana-data`,
|
|
40
|
+
},
|
|
41
|
+
env: {
|
|
42
|
+
GF_SECURITY_ADMIN_PASSWORD: config.adminPassword ?? "admin",
|
|
43
|
+
GF_AUTH_ANONYMOUS_ENABLED: String(config.anonymousAccess ?? true),
|
|
44
|
+
GF_AUTH_ANONYMOUS_ORG_ROLE: "Viewer",
|
|
45
|
+
GF_PLUGINS_PREINSTALL: "tkurki-signalk-datasource",
|
|
46
|
+
GF_FEATURE_TOGGLES_DISABLE: "backgroundPluginInstaller",
|
|
47
|
+
GF_SECURITY_ALLOW_EMBEDDING: "true",
|
|
48
|
+
},
|
|
49
|
+
restart: "unless-stopped",
|
|
50
|
+
};
|
|
51
|
+
const configHash = JSON.stringify({
|
|
52
|
+
tag: containerConfig.tag,
|
|
53
|
+
ports: containerConfig.ports,
|
|
54
|
+
env: containerConfig.env,
|
|
55
|
+
networkMode: containerConfig.networkMode,
|
|
56
|
+
});
|
|
57
|
+
const hashFile = `${dataDir}.container-hash`;
|
|
58
|
+
let lastHash = "";
|
|
59
|
+
try {
|
|
60
|
+
lastHash = (0, fs_1.readFileSync)(hashFile, "utf8");
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// first run
|
|
64
|
+
}
|
|
65
|
+
const state = await containers.getState("signalk-grafana");
|
|
66
|
+
if (state !== "missing" && configHash !== lastHash) {
|
|
67
|
+
app.setPluginStatus("Recreating Grafana container (config changed)...");
|
|
68
|
+
await containers.remove("signalk-grafana");
|
|
69
|
+
}
|
|
70
|
+
app.setPluginStatus("Starting Grafana container...");
|
|
71
|
+
await containers.ensureRunning("signalk-grafana", containerConfig);
|
|
72
|
+
(0, fs_1.writeFileSync)(hashFile, configHash);
|
|
73
|
+
const grafanaUrl = `http://127.0.0.1:${config.grafanaPort}`;
|
|
74
|
+
const healthDeadline = Date.now() + 30000;
|
|
75
|
+
while (Date.now() < healthDeadline) {
|
|
76
|
+
try {
|
|
77
|
+
const res = await fetch(`${grafanaUrl}/api/health`, {
|
|
78
|
+
signal: AbortSignal.timeout(2000),
|
|
79
|
+
});
|
|
80
|
+
if (res.ok)
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// not ready yet
|
|
85
|
+
}
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
87
|
+
}
|
|
88
|
+
app.setPluginStatus(`Grafana running at port ${config.grafanaPort}`);
|
|
89
|
+
}
|
|
90
|
+
const plugin = {
|
|
91
|
+
id: "signalk-grafana",
|
|
92
|
+
name: "Grafana Dashboards",
|
|
93
|
+
schema: schema_1.ConfigSchema,
|
|
94
|
+
start(config) {
|
|
95
|
+
asyncStart(config).catch((err) => {
|
|
96
|
+
app.setPluginError(`Startup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
async stop() {
|
|
100
|
+
if (currentConfig) {
|
|
101
|
+
const containers = globalThis.__signalk_containerManager;
|
|
102
|
+
if (containers) {
|
|
103
|
+
try {
|
|
104
|
+
await containers.stop("signalk-grafana");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// may already be stopped
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
currentConfig = null;
|
|
112
|
+
},
|
|
113
|
+
registerWithRouter(router) {
|
|
114
|
+
router.get("/api/status", async (_req, res) => {
|
|
115
|
+
try {
|
|
116
|
+
if (!currentConfig) {
|
|
117
|
+
res.status(503).json({ status: "not_running" });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const grafanaUrl = `http://127.0.0.1:${currentConfig.grafanaPort}`;
|
|
121
|
+
const healthRes = await fetch(`${grafanaUrl}/api/health`, {
|
|
122
|
+
signal: AbortSignal.timeout(3000),
|
|
123
|
+
});
|
|
124
|
+
if (healthRes.ok) {
|
|
125
|
+
const health = (await healthRes.json());
|
|
126
|
+
res.json({
|
|
127
|
+
status: "running",
|
|
128
|
+
port: currentConfig.grafanaPort,
|
|
129
|
+
version: health.version || "unknown",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
res.status(503).json({ status: "unhealthy" });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
res.status(503).json({ status: "not_running" });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
router.get("/api/versions", async (_req, res) => {
|
|
141
|
+
try {
|
|
142
|
+
const ghRes = await fetch("https://api.github.com/repos/grafana/grafana/releases?per_page=10", {
|
|
143
|
+
headers: { Accept: "application/vnd.github+json" },
|
|
144
|
+
signal: AbortSignal.timeout(10000),
|
|
145
|
+
});
|
|
146
|
+
if (!ghRes.ok) {
|
|
147
|
+
res.status(502).json({ error: "Failed to fetch releases" });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const releases = (await ghRes.json());
|
|
151
|
+
const versions = releases
|
|
152
|
+
.filter((r) => !r.draft)
|
|
153
|
+
.map((r) => ({
|
|
154
|
+
tag: r.tag_name.replace(/^v/, ""),
|
|
155
|
+
prerelease: r.prerelease,
|
|
156
|
+
}));
|
|
157
|
+
res.json(versions);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
res.status(500).json({
|
|
161
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
router.get("/api/update/check", async (_req, res) => {
|
|
166
|
+
try {
|
|
167
|
+
if (!currentConfig) {
|
|
168
|
+
res.status(503).json({ error: "Plugin not running" });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const grafanaUrl = `http://127.0.0.1:${currentConfig.grafanaPort}`;
|
|
172
|
+
let currentVersion = "unknown";
|
|
173
|
+
try {
|
|
174
|
+
const healthRes = await fetch(`${grafanaUrl}/api/health`, {
|
|
175
|
+
signal: AbortSignal.timeout(3000),
|
|
176
|
+
});
|
|
177
|
+
if (healthRes.ok) {
|
|
178
|
+
const health = (await healthRes.json());
|
|
179
|
+
currentVersion = health.version || "unknown";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// not reachable
|
|
184
|
+
}
|
|
185
|
+
const ghRes = await fetch("https://api.github.com/repos/grafana/grafana/releases?per_page=5", {
|
|
186
|
+
headers: { Accept: "application/vnd.github+json" },
|
|
187
|
+
signal: AbortSignal.timeout(10000),
|
|
188
|
+
});
|
|
189
|
+
let latestVersion = "unknown";
|
|
190
|
+
if (ghRes.ok) {
|
|
191
|
+
const releases = (await ghRes.json());
|
|
192
|
+
const stable = releases.find((r) => !r.draft && !r.prerelease);
|
|
193
|
+
if (stable)
|
|
194
|
+
latestVersion = stable.tag_name.replace(/^v/, "");
|
|
195
|
+
}
|
|
196
|
+
const isNewerAvailable = (current, latest) => {
|
|
197
|
+
const pc = current.split(".").map(Number);
|
|
198
|
+
const pl = latest.split(".").map(Number);
|
|
199
|
+
for (let i = 0; i < Math.max(pc.length, pl.length); i++) {
|
|
200
|
+
const vc = pc[i] ?? 0;
|
|
201
|
+
const vl = pl[i] ?? 0;
|
|
202
|
+
if (vl > vc)
|
|
203
|
+
return true;
|
|
204
|
+
if (vl < vc)
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
};
|
|
209
|
+
const updateAvailable = currentVersion !== "unknown" &&
|
|
210
|
+
latestVersion !== "unknown" &&
|
|
211
|
+
isNewerAvailable(currentVersion, latestVersion);
|
|
212
|
+
res.json({ currentVersion, latestVersion, updateAvailable });
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
res.status(500).json({
|
|
216
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
router.post("/api/update/apply", async (_req, res) => {
|
|
221
|
+
try {
|
|
222
|
+
const containers = globalThis.__signalk_containerManager;
|
|
223
|
+
if (!containers || !containers.getRuntime()) {
|
|
224
|
+
res.status(503).json({ error: "Container manager not available" });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const ghRes = await fetch("https://api.github.com/repos/grafana/grafana/releases?per_page=5", {
|
|
228
|
+
headers: { Accept: "application/vnd.github+json" },
|
|
229
|
+
signal: AbortSignal.timeout(10000),
|
|
230
|
+
});
|
|
231
|
+
if (!ghRes.ok) {
|
|
232
|
+
res.status(502).json({ error: "Failed to fetch releases" });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const releases = (await ghRes.json());
|
|
236
|
+
const stable = releases.find((r) => !r.draft && !r.prerelease);
|
|
237
|
+
if (!stable) {
|
|
238
|
+
res.status(404).json({ error: "No stable release found" });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const newTag = stable.tag_name.replace(/^v/, "");
|
|
242
|
+
app.setPluginStatus(`Pulling Grafana ${newTag}...`);
|
|
243
|
+
await containers.pullImage(`grafana/grafana:${newTag}`);
|
|
244
|
+
app.setPluginStatus("Replacing container...");
|
|
245
|
+
await containers.remove("signalk-grafana");
|
|
246
|
+
app.setPluginStatus(`Starting Grafana ${newTag}...`);
|
|
247
|
+
await containers.ensureRunning("signalk-grafana", {
|
|
248
|
+
image: "grafana/grafana",
|
|
249
|
+
tag: newTag,
|
|
250
|
+
ports: {
|
|
251
|
+
"3000/tcp": `${currentConfig?.bindToAllInterfaces ? "0.0.0.0" : "127.0.0.1"}:${currentConfig?.grafanaPort ?? 3001}`,
|
|
252
|
+
},
|
|
253
|
+
networkMode: currentConfig?.networkName ?? "sk-network",
|
|
254
|
+
volumes: {
|
|
255
|
+
"/etc/grafana/provisioning": `${app.getDataDirPath()}/provisioning`,
|
|
256
|
+
"/var/lib/grafana/dashboards": `${app.getDataDirPath()}/dashboards`,
|
|
257
|
+
"/var/lib/grafana": `${app.getDataDirPath()}/grafana-data`,
|
|
258
|
+
},
|
|
259
|
+
env: {
|
|
260
|
+
GF_SECURITY_ADMIN_PASSWORD: currentConfig?.adminPassword ?? "admin",
|
|
261
|
+
GF_AUTH_ANONYMOUS_ENABLED: String(currentConfig?.anonymousAccess ?? true),
|
|
262
|
+
GF_AUTH_ANONYMOUS_ORG_ROLE: "Viewer",
|
|
263
|
+
GF_PLUGINS_PREINSTALL: "tkurki-signalk-datasource",
|
|
264
|
+
GF_FEATURE_TOGGLES_DISABLE: "backgroundPluginInstaller",
|
|
265
|
+
GF_SECURITY_ALLOW_EMBEDDING: "true",
|
|
266
|
+
},
|
|
267
|
+
restart: "unless-stopped",
|
|
268
|
+
});
|
|
269
|
+
if (currentConfig) {
|
|
270
|
+
currentConfig.grafanaVersion = newTag;
|
|
271
|
+
}
|
|
272
|
+
app.setPluginStatus(`Grafana ${newTag} running at port ${currentConfig?.grafanaPort ?? 3001}`);
|
|
273
|
+
res.json({
|
|
274
|
+
status: "updated",
|
|
275
|
+
newVersion: newTag,
|
|
276
|
+
message: `Updated to Grafana ${newTag}. Container running.`,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
res.status(500).json({
|
|
281
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
router.post("/api/set-password", async (req, res) => {
|
|
286
|
+
try {
|
|
287
|
+
const containers = globalThis.__signalk_containerManager;
|
|
288
|
+
if (!containers || !containers.getRuntime()) {
|
|
289
|
+
res.status(503).json({ error: "Container manager not available" });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const password = currentConfig?.adminPassword ?? "admin";
|
|
293
|
+
const result = await containers.execInContainer("signalk-grafana", [
|
|
294
|
+
"grafana",
|
|
295
|
+
"cli",
|
|
296
|
+
"admin",
|
|
297
|
+
"reset-admin-password",
|
|
298
|
+
password,
|
|
299
|
+
]);
|
|
300
|
+
if (result.exitCode === 0) {
|
|
301
|
+
res.json({
|
|
302
|
+
status: "ok",
|
|
303
|
+
message: "Admin password updated.",
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
res.status(500).json({
|
|
308
|
+
error: result.stderr || "Failed to set password",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
res.status(500).json({
|
|
314
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
return plugin;
|
|
321
|
+
};
|
|
322
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateProvisioning = generateProvisioning;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
function generateProvisioning(dataDir, config) {
|
|
7
|
+
const provDir = (0, path_1.join)(dataDir, "provisioning");
|
|
8
|
+
const dsDir = (0, path_1.join)(provDir, "datasources");
|
|
9
|
+
const dbProvDir = (0, path_1.join)(provDir, "dashboards");
|
|
10
|
+
const dbDir = (0, path_1.join)(dataDir, "dashboards");
|
|
11
|
+
const grafanaDataDir = (0, path_1.join)(dataDir, "grafana-data");
|
|
12
|
+
(0, fs_1.mkdirSync)(dsDir, { recursive: true });
|
|
13
|
+
(0, fs_1.mkdirSync)(dbProvDir, { recursive: true });
|
|
14
|
+
(0, fs_1.mkdirSync)(dbDir, { recursive: true });
|
|
15
|
+
(0, fs_1.mkdirSync)(grafanaDataDir, { recursive: true });
|
|
16
|
+
const questdbHost = `sk-${config.questdbContainerName}`;
|
|
17
|
+
const datasourceYaml = `apiVersion: 1
|
|
18
|
+
datasources:
|
|
19
|
+
- name: QuestDB
|
|
20
|
+
type: postgres
|
|
21
|
+
url: ${questdbHost}:${config.questdbPgPort}
|
|
22
|
+
user: admin
|
|
23
|
+
database: qdb
|
|
24
|
+
access: proxy
|
|
25
|
+
isDefault: true
|
|
26
|
+
editable: true
|
|
27
|
+
jsonData:
|
|
28
|
+
sslmode: disable
|
|
29
|
+
postgresVersion: 1200
|
|
30
|
+
secureJsonData:
|
|
31
|
+
password: quest
|
|
32
|
+
`;
|
|
33
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(dsDir, "questdb.yaml"), datasourceYaml);
|
|
34
|
+
const skHost = config.signalkUrl?.replace(/^https?:\/\//, "") ||
|
|
35
|
+
`host.containers.internal:${process.env.PORT || 3000}`;
|
|
36
|
+
const signalkDsYaml = `apiVersion: 1
|
|
37
|
+
datasources:
|
|
38
|
+
- name: Signal K
|
|
39
|
+
type: tkurki-signalk-datasource
|
|
40
|
+
access: proxy
|
|
41
|
+
url: http://${skHost}
|
|
42
|
+
isDefault: false
|
|
43
|
+
editable: true
|
|
44
|
+
jsonData:
|
|
45
|
+
context: self
|
|
46
|
+
hostname: ${skHost}
|
|
47
|
+
ssl: false
|
|
48
|
+
`;
|
|
49
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(dsDir, "signalk.yaml"), signalkDsYaml);
|
|
50
|
+
const dashboardProviderYaml = `apiVersion: 1
|
|
51
|
+
providers:
|
|
52
|
+
- name: Signal K
|
|
53
|
+
orgId: 1
|
|
54
|
+
folder: Signal K
|
|
55
|
+
type: file
|
|
56
|
+
disableDeletion: false
|
|
57
|
+
editable: true
|
|
58
|
+
updateIntervalSeconds: 30
|
|
59
|
+
allowUiUpdates: true
|
|
60
|
+
options:
|
|
61
|
+
path: /var/lib/grafana/dashboards
|
|
62
|
+
`;
|
|
63
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(dbProvDir, "signalk.yaml"), dashboardProviderYaml);
|
|
64
|
+
copyDashboards(dbDir);
|
|
65
|
+
}
|
|
66
|
+
function copyDashboards(destDir) {
|
|
67
|
+
const srcDir = (0, path_1.join)(__dirname, "dashboards");
|
|
68
|
+
if (!(0, fs_1.existsSync)(srcDir))
|
|
69
|
+
return;
|
|
70
|
+
(0, fs_1.cpSync)(srcDir, destDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=provisioning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../src/provisioning.ts"],"names":[],"mappings":";;AAIA,oDAoEC;AAxED,2BAAkE;AAClE,+BAA4B;AAG5B,SAAgB,oBAAoB,CAAC,OAAe,EAAE,MAAc;IAClE,MAAM,OAAO,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAErD,IAAA,cAAS,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,IAAA,cAAS,EAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,IAAA,cAAS,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,IAAA,cAAS,EAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,oBAAoB,EAAE,CAAC;IAExD,MAAM,cAAc,GAAG;;;;WAId,WAAW,IAAI,MAAM,CAAC,aAAa;;;;;;;;;;;CAW7C,CAAC;IAEA,IAAA,kBAAa,EAAC,IAAA,WAAI,EAAC,KAAK,EAAE,cAAc,CAAC,EAAE,cAAc,CAAC,CAAC;IAE3D,MAAM,MAAM,GACV,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAC9C,4BAA4B,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IACzD,MAAM,aAAa,GAAG;;;;;kBAKN,MAAM;;;;;kBAKN,MAAM;;CAEvB,CAAC;IACA,IAAA,kBAAa,EAAC,IAAA,WAAI,EAAC,KAAK,EAAE,cAAc,CAAC,EAAE,aAAa,CAAC,CAAC;IAE1D,MAAM,qBAAqB,GAAG;;;;;;;;;;;;CAY/B,CAAC;IAEA,IAAA,kBAAa,EAAC,IAAA,WAAI,EAAC,SAAS,EAAE,cAAc,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAEtE,cAAc,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,MAAM,GAAG,IAAA,WAAI,EAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAA,eAAU,EAAC,MAAM,CAAC;QAAE,OAAO;IAEhC,IAAA,WAAM,EAAC,MAAM,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = require("node:test");
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const os_1 = require("os");
|
|
11
|
+
const provisioning_1 = require("../provisioning");
|
|
12
|
+
(0, node_test_1.describe)("generateProvisioning", () => {
|
|
13
|
+
let tempDir;
|
|
14
|
+
(0, node_test_1.afterEach)(() => {
|
|
15
|
+
if (tempDir)
|
|
16
|
+
(0, fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
(0, node_test_1.it)("creates datasource YAML with QuestDB connection", () => {
|
|
19
|
+
tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "grafana-test-"));
|
|
20
|
+
(0, provisioning_1.generateProvisioning)(tempDir, {
|
|
21
|
+
grafanaPort: 3001,
|
|
22
|
+
grafanaVersion: "latest",
|
|
23
|
+
adminPassword: "admin",
|
|
24
|
+
anonymousAccess: true,
|
|
25
|
+
questdbContainerName: "signalk-questdb",
|
|
26
|
+
questdbPgPort: 8812,
|
|
27
|
+
networkName: "sk-network",
|
|
28
|
+
signalkUrl: "",
|
|
29
|
+
bindToAllInterfaces: false,
|
|
30
|
+
});
|
|
31
|
+
const dsFile = (0, path_1.join)(tempDir, "provisioning/datasources/questdb.yaml");
|
|
32
|
+
strict_1.default.ok((0, fs_1.existsSync)(dsFile), "datasource file should exist");
|
|
33
|
+
const content = (0, fs_1.readFileSync)(dsFile, "utf8");
|
|
34
|
+
strict_1.default.ok(content.includes("sk-signalk-questdb:8812"));
|
|
35
|
+
strict_1.default.ok(content.includes("type: postgres"));
|
|
36
|
+
strict_1.default.ok(content.includes("user: admin"));
|
|
37
|
+
strict_1.default.ok(content.includes("database: qdb"));
|
|
38
|
+
strict_1.default.ok(content.includes("sslmode: disable"));
|
|
39
|
+
});
|
|
40
|
+
(0, node_test_1.it)("creates dashboard provider YAML", () => {
|
|
41
|
+
tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "grafana-test-"));
|
|
42
|
+
(0, provisioning_1.generateProvisioning)(tempDir, {
|
|
43
|
+
grafanaPort: 3001,
|
|
44
|
+
grafanaVersion: "latest",
|
|
45
|
+
adminPassword: "admin",
|
|
46
|
+
anonymousAccess: true,
|
|
47
|
+
questdbContainerName: "signalk-questdb",
|
|
48
|
+
questdbPgPort: 8812,
|
|
49
|
+
networkName: "sk-network",
|
|
50
|
+
signalkUrl: "",
|
|
51
|
+
bindToAllInterfaces: false,
|
|
52
|
+
});
|
|
53
|
+
const provFile = (0, path_1.join)(tempDir, "provisioning/dashboards/signalk.yaml");
|
|
54
|
+
strict_1.default.ok((0, fs_1.existsSync)(provFile), "dashboard provider file should exist");
|
|
55
|
+
const content = (0, fs_1.readFileSync)(provFile, "utf8");
|
|
56
|
+
strict_1.default.ok(content.includes("Signal K"));
|
|
57
|
+
strict_1.default.ok(content.includes("/var/lib/grafana/dashboards"));
|
|
58
|
+
});
|
|
59
|
+
(0, node_test_1.it)("creates dashboard directory", () => {
|
|
60
|
+
tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "grafana-test-"));
|
|
61
|
+
(0, provisioning_1.generateProvisioning)(tempDir, {
|
|
62
|
+
grafanaPort: 3001,
|
|
63
|
+
grafanaVersion: "latest",
|
|
64
|
+
adminPassword: "admin",
|
|
65
|
+
anonymousAccess: true,
|
|
66
|
+
questdbContainerName: "signalk-questdb",
|
|
67
|
+
questdbPgPort: 8812,
|
|
68
|
+
networkName: "sk-network",
|
|
69
|
+
signalkUrl: "",
|
|
70
|
+
bindToAllInterfaces: false,
|
|
71
|
+
});
|
|
72
|
+
strict_1.default.ok((0, fs_1.existsSync)((0, path_1.join)(tempDir, "dashboards")), "dashboards directory should exist");
|
|
73
|
+
});
|
|
74
|
+
(0, node_test_1.it)("uses custom QuestDB container name and port", () => {
|
|
75
|
+
tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "grafana-test-"));
|
|
76
|
+
(0, provisioning_1.generateProvisioning)(tempDir, {
|
|
77
|
+
grafanaPort: 3001,
|
|
78
|
+
grafanaVersion: "latest",
|
|
79
|
+
adminPassword: "admin",
|
|
80
|
+
anonymousAccess: true,
|
|
81
|
+
questdbContainerName: "my-questdb",
|
|
82
|
+
questdbPgPort: 9999,
|
|
83
|
+
networkName: "custom-net",
|
|
84
|
+
signalkUrl: "",
|
|
85
|
+
bindToAllInterfaces: false,
|
|
86
|
+
});
|
|
87
|
+
const content = (0, fs_1.readFileSync)((0, path_1.join)(tempDir, "provisioning/datasources/questdb.yaml"), "utf8");
|
|
88
|
+
strict_1.default.ok(content.includes("sk-my-questdb:9999"));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=provisioning.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.test.js","sourceRoot":"","sources":["../../src/test/provisioning.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signalk-grafana",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Managed Grafana with auto-provisioned QuestDB dashboards for Signal K",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"signalk-node-server-plugin",
|
|
7
|
+
"signalk-plugin-configurator"
|
|
8
|
+
],
|
|
9
|
+
"signalk": {
|
|
10
|
+
"displayName": "Grafana Dashboards",
|
|
11
|
+
"appIcon": "./icon.svg"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && webpack",
|
|
16
|
+
"build:config": "webpack",
|
|
17
|
+
"watch": "tsc --watch",
|
|
18
|
+
"prettier": "prettier --write .",
|
|
19
|
+
"lint": "eslint --fix",
|
|
20
|
+
"format": "npm run prettier && npm run lint",
|
|
21
|
+
"ci-lint": "eslint && prettier --check .",
|
|
22
|
+
"test": "node --test 'dist/test/**/*.test.js'",
|
|
23
|
+
"build:all": "npm run build && npm test",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/dirkwa/signalk-grafana"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@babel/core": "^7.29.0",
|
|
36
|
+
"@babel/preset-react": "^7.28.5",
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
38
|
+
"@signalk/server-api": "latest",
|
|
39
|
+
"@types/express": "^5.0.0",
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"babel-loader": "^10.1.1",
|
|
42
|
+
"eslint": "^10.2.0",
|
|
43
|
+
"eslint-config-prettier": "^10.1.8",
|
|
44
|
+
"globals": "^17.4.0",
|
|
45
|
+
"prettier": "^3.8.1",
|
|
46
|
+
"react": "^19.2.4",
|
|
47
|
+
"react-dom": "^19.2.4",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"typescript-eslint": "^8.58.0",
|
|
50
|
+
"webpack": "^5.105.4",
|
|
51
|
+
"webpack-cli": "^7.0.2"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"signalk-container": ">=0.1.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"signalk-container": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/public/540.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! For license information please see 540.js.LICENSE.txt */
|
|
2
|
+
"use strict";(self.webpackChunksignalk_grafana=self.webpackChunksignalk_grafana||[]).push([[540],{869(e,t){var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),a=Symbol.for("react.profiler"),c=Symbol.for("react.consumer"),i=Symbol.for("react.context"),s=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),l=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),y=Symbol.for("react.activity"),d=Symbol.iterator,h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,b={};function m(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||h}function v(){}function S(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||h}m.prototype.isReactComponent={},m.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},m.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},v.prototype=m.prototype;var E=S.prototype=new v;E.constructor=S,_(E,m.prototype),E.isPureReactComponent=!0;var g=Array.isArray;function w(){}var k={H:null,A:null,T:null,S:null},H=Object.prototype.hasOwnProperty;function j(e,t,r){var o=r.ref;return{$$typeof:n,type:e,key:t,ref:void 0!==o?o:null,props:r}}function C(e){return"object"==typeof e&&null!==e&&e.$$typeof===n}var R=/\/+/g;function $(e,t){return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,function(e){return r[e]})):t.toString(36);var n,r}function T(e,t,o,u,a){var c=typeof e;"undefined"!==c&&"boolean"!==c||(e=null);var i,s,f=!1;if(null===e)f=!0;else switch(c){case"bigint":case"string":case"number":f=!0;break;case"object":switch(e.$$typeof){case n:case r:f=!0;break;case p:return T((f=e._init)(e._payload),t,o,u,a)}}if(f)return a=a(e),f=""===u?"."+$(e,0):u,g(a)?(o="",null!=f&&(o=f.replace(R,"$&/")+"/"),T(a,t,o,"",function(e){return e})):null!=a&&(C(a)&&(i=a,s=o+(null==a.key||e&&e.key===a.key?"":(""+a.key).replace(R,"$&/")+"/")+f,a=j(i.type,s,i.props)),t.push(a)),1;f=0;var l,y=""===u?".":u+":";if(g(e))for(var h=0;h<e.length;h++)f+=T(u=e[h],t,o,c=y+$(u,h),a);else if("function"==typeof(h=null===(l=e)||"object"!=typeof l?null:"function"==typeof(l=d&&l[d]||l["@@iterator"])?l:null))for(e=h.call(e),h=0;!(u=e.next()).done;)f+=T(u=u.value,t,o,c=y+$(u,h++),a);else if("object"===c){if("function"==typeof e.then)return T(function(e){switch(e.status){case"fulfilled":return e.value;case"rejected":throw e.reason;default:switch("string"==typeof e.status?e.then(w,w):(e.status="pending",e.then(function(t){"pending"===e.status&&(e.status="fulfilled",e.value=t)},function(t){"pending"===e.status&&(e.status="rejected",e.reason=t)})),e.status){case"fulfilled":return e.value;case"rejected":throw e.reason}}throw e}(e),t,o,u,a);throw t=String(e),Error("Objects are not valid as a React child (found: "+("[object Object]"===t?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.")}return f}function x(e,t,n){if(null==e)return e;var r=[],o=0;return T(e,r,"","",function(e){return t.call(n,e,o++)}),r}function A(e){if(-1===e._status){var t=e._result;(t=t()).then(function(t){0!==e._status&&-1!==e._status||(e._status=1,e._result=t)},function(t){0!==e._status&&-1!==e._status||(e._status=2,e._result=t)}),-1===e._status&&(e._status=0,e._result=t)}if(1===e._status)return e._result.default;throw e._result}var O="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)},I={map:x,forEach:function(e,t,n){x(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return x(e,function(){t++}),t},toArray:function(e){return x(e,function(e){return e})||[]},only:function(e){if(!C(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};t.Activity=y,t.Children=I,t.Component=m,t.Fragment=o,t.Profiler=a,t.PureComponent=S,t.StrictMode=u,t.Suspense=f,t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=k,t.__COMPILER_RUNTIME={__proto__:null,c:function(e){return k.H.useMemoCache(e)}},t.cache=function(e){return function(){return e.apply(null,arguments)}},t.cacheSignal=function(){return null},t.cloneElement=function(e,t,n){if(null==e)throw Error("The argument must be a React element, but you passed "+e+".");var r=_({},e.props),o=e.key;if(null!=t)for(u in void 0!==t.key&&(o=""+t.key),t)!H.call(t,u)||"key"===u||"__self"===u||"__source"===u||"ref"===u&&void 0===t.ref||(r[u]=t[u]);var u=arguments.length-2;if(1===u)r.children=n;else if(1<u){for(var a=Array(u),c=0;c<u;c++)a[c]=arguments[c+2];r.children=a}return j(e.type,o,r)},t.createContext=function(e){return(e={$$typeof:i,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider=e,e.Consumer={$$typeof:c,_context:e},e},t.createElement=function(e,t,n){var r,o={},u=null;if(null!=t)for(r in void 0!==t.key&&(u=""+t.key),t)H.call(t,r)&&"key"!==r&&"__self"!==r&&"__source"!==r&&(o[r]=t[r]);var a=arguments.length-2;if(1===a)o.children=n;else if(1<a){for(var c=Array(a),i=0;i<a;i++)c[i]=arguments[i+2];o.children=c}if(e&&e.defaultProps)for(r in a=e.defaultProps)void 0===o[r]&&(o[r]=a[r]);return j(e,u,o)},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:s,render:e}},t.isValidElement=C,t.lazy=function(e){return{$$typeof:p,_payload:{_status:-1,_result:e},_init:A}},t.memo=function(e,t){return{$$typeof:l,type:e,compare:void 0===t?null:t}},t.startTransition=function(e){var t=k.T,n={};k.T=n;try{var r=e(),o=k.S;null!==o&&o(n,r),"object"==typeof r&&null!==r&&"function"==typeof r.then&&r.then(w,O)}catch(e){O(e)}finally{null!==t&&null!==n.types&&(t.types=n.types),k.T=t}},t.unstable_useCacheRefresh=function(){return k.H.useCacheRefresh()},t.use=function(e){return k.H.use(e)},t.useActionState=function(e,t,n){return k.H.useActionState(e,t,n)},t.useCallback=function(e,t){return k.H.useCallback(e,t)},t.useContext=function(e){return k.H.useContext(e)},t.useDebugValue=function(){},t.useDeferredValue=function(e,t){return k.H.useDeferredValue(e,t)},t.useEffect=function(e,t){return k.H.useEffect(e,t)},t.useEffectEvent=function(e){return k.H.useEffectEvent(e)},t.useId=function(){return k.H.useId()},t.useImperativeHandle=function(e,t,n){return k.H.useImperativeHandle(e,t,n)},t.useInsertionEffect=function(e,t){return k.H.useInsertionEffect(e,t)},t.useLayoutEffect=function(e,t){return k.H.useLayoutEffect(e,t)},t.useMemo=function(e,t){return k.H.useMemo(e,t)},t.useOptimistic=function(e,t){return k.H.useOptimistic(e,t)},t.useReducer=function(e,t,n){return k.H.useReducer(e,t,n)},t.useRef=function(e){return k.H.useRef(e)},t.useState=function(e){return k.H.useState(e)},t.useSyncExternalStore=function(e,t,n){return k.H.useSyncExternalStore(e,t,n)},t.useTransition=function(){return k.H.useTransition()},t.version="19.2.4"},540(e,t,n){e.exports=n(869)}}]);
|
package/public/805.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(self.webpackChunksignalk_grafana=self.webpackChunksignalk_grafana||[]).push([[805],{805(e,t,a){a.r(t),a.d(t,{default:()=>r});var n=a(231);const l={root:{fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',color:"#333",padding:"16px 0"},sectionTitle:{fontSize:13,fontWeight:600,color:"#888",textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:10,marginTop:24},btn:{display:"inline-flex",alignItems:"center",gap:8,padding:"8px 16px",border:"none",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},btnPrimary:{background:"#3b82f6",color:"#fff"},btnSave:{background:"#3b82f6",color:"#fff"},status:{marginTop:8,fontSize:12,minHeight:18},card:{display:"flex",alignItems:"center",gap:14,padding:"14px 18px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,marginBottom:12},cardIcon:{width:44,height:44,borderRadius:10,display:"flex",alignItems:"center",justifyContent:"center",fontSize:20,fontWeight:700,flexShrink:0},cardInfo:{flex:1},cardTitle:{fontSize:15,fontWeight:600,color:"#333"},cardMeta:{fontSize:12,color:"#888"},stateIndicator:{width:10,height:10,borderRadius:"50%",flexShrink:0},fieldRow:{display:"flex",alignItems:"center",gap:12,marginBottom:10},label:{fontSize:13,fontWeight:500,color:"#555",width:180,flexShrink:0},input:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",width:200},inputSmall:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",width:80},checkbox:{width:16,height:16,accentColor:"#3b82f6"},hint:{fontSize:11,color:"#aaa",marginLeft:8},link:{color:"#3b82f6",textDecoration:"none",fontSize:13,fontWeight:600}};function r({configuration:e,save:t}){const a=e||{},[r,o]=(0,n.useState)(a.grafanaPort||3001),[i,s]=(0,n.useState)(a.grafanaVersion||"latest"),[c,d]=(0,n.useState)(a.adminPassword||"admin"),[u,p]=(0,n.useState)(!1!==a.anonymousAccess),[f,g]=(0,n.useState)(a.questdbContainerName||"signalk-questdb"),[m,y]=(0,n.useState)(a.questdbPgPort||8812),[b,h]=(0,n.useState)(a.networkName||"sk-network"),[v,E]=(0,n.useState)(a.signalkUrl||""),[k,S]=(0,n.useState)(a.bindToAllInterfaces||!1),[w,x]=(0,n.useState)(null),[C,I]=(0,n.useState)(!0),[R,T]=(0,n.useState)(""),[z,P]=(0,n.useState)(!1),[G,N]=(0,n.useState)([]),[j,A]=(0,n.useState)(!1),[U,B]=(0,n.useState)(null),[V,W]=(0,n.useState)(!1),[$,q]=(0,n.useState)(!1),F=(0,n.useCallback)(async()=>{A(!0);try{const e=await fetch("/plugins/signalk-grafana/api/versions");e.ok&&N(await e.json())}catch{}A(!1)},[]),_=(0,n.useCallback)(async()=>{try{const e=await fetch("/plugins/signalk-grafana/api/status");e.ok?x(await e.json()):x({status:"not_running"})}catch{x({status:"not_running"})}I(!1)},[]);(0,n.useEffect)(()=>{_(),F();const e=setInterval(_,5e3);return()=>clearInterval(e)},[_,F]);const D=w&&"running"===w.status;return n.createElement("div",{style:l.root},n.createElement("div",{style:l.sectionTitle},"Grafana Status"),C?n.createElement("div",{style:{padding:"16px",color:"#999",fontSize:13}},"Checking Grafana..."):D?n.createElement("div",{style:l.card},n.createElement("div",{style:{...l.cardIcon,background:"#f46800",color:"#fff"}},"G"),n.createElement("div",{style:l.cardInfo},n.createElement("div",{style:l.cardTitle},"Grafana"),n.createElement("div",{style:l.cardMeta},"v",w.version," · Port ",w.port)),n.createElement("a",{href:`http://${window.location.hostname}:${w.port}`,target:"_blank",rel:"noopener noreferrer",style:l.link},"Open Grafana ↗"),n.createElement("div",{style:{...l.stateIndicator,background:"#10b981"},title:"Running"})):n.createElement("div",{style:l.card},n.createElement("div",{style:{...l.cardIcon,background:"#fef2f2",color:"#ef4444"}},"G"),n.createElement("div",{style:l.cardInfo},n.createElement("div",{style:l.cardTitle},"Grafana"),n.createElement("div",{style:l.cardMeta},"Not running")),n.createElement("div",{style:{...l.stateIndicator,background:"#ef4444"}})),D&&n.createElement("div",{style:{display:"flex",alignItems:"center",gap:10,marginBottom:12}},U&&U.updateAvailable?n.createElement(n.Fragment,null,n.createElement("span",{style:{fontSize:13}},"v",U.currentVersion," →"," ",n.createElement("strong",null,"v",U.latestVersion)," available"),n.createElement("button",{style:{...l.btn,...l.btnPrimary,padding:"4px 12px",fontSize:12,...$?{opacity:.5,cursor:"not-allowed"}:{}},onClick:async()=>{q(!0),T("Pulling new image and restarting..."),P(!1);try{const e=await fetch("/plugins/signalk-grafana/api/update/apply",{method:"POST"});if(e.ok){const t=await e.json();T(t.message),B(null),t.newVersion&&s(t.newVersion),_()}else{const t=await e.json().catch(()=>({error:e.statusText}));T(`Update failed: ${t.error}`),P(!0)}}catch(e){T(`Update failed: ${e.message}`),P(!0)}q(!1)},disabled:$},$?"Updating...":"Update Grafana")):U&&!U.updateAvailable?n.createElement("span",{style:{fontSize:12,color:"#888"}},"v",U.currentVersion," (up to date)"):n.createElement("button",{style:{...l.btn,padding:"4px 12px",fontSize:12,background:"#f1f5f9",color:"#475569",border:"1px solid #e2e8f0",...V?{opacity:.5,cursor:"not-allowed"}:{}},onClick:async()=>{W(!0);try{const e=await fetch("/plugins/signalk-grafana/api/update/check");e.ok&&B(await e.json())}catch{}W(!1)},disabled:V},V?"Checking...":"Check for updates")),n.createElement("div",{style:l.sectionTitle},"Settings"),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Grafana port"),n.createElement("input",{style:l.inputSmall,type:"number",value:r,onChange:e=>o(Number(e.target.value))}),n.createElement("span",{style:l.hint},"avoid 3000 if Signal K uses it")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Image version"),n.createElement("select",{style:{...l.input,width:"auto",minWidth:200},value:i,onChange:e=>s(e.target.value)},n.createElement("option",{value:"latest"},"latest (recommended)"),G.filter(e=>e.prerelease).slice(0,2).map(e=>n.createElement("option",{key:e.tag,value:e.tag},e.tag," (pre-release)")),G.filter(e=>!e.prerelease).slice(0,3).map((e,t)=>n.createElement("option",{key:e.tag,value:e.tag},e.tag,0===t?" (current stable)":""))),j&&n.createElement("span",{style:l.hint},"loading releases..."),n.createElement("button",{style:{...l.btn,...l.btnPrimary,padding:"4px 10px",fontSize:11},onClick:F},"↻")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Admin password"),n.createElement("input",{style:l.input,type:"text",value:c,onChange:e=>d(e.target.value)}),n.createElement("button",{style:{...l.btn,...l.btnPrimary,padding:"4px 12px",fontSize:12},onClick:async()=>{T("Setting password..."),P(!1);try{const e=await fetch("/plugins/signalk-grafana/api/set-password",{method:"POST"});if(e.ok){const t=await e.json();T(t.message)}else{const t=await e.json().catch(()=>({error:e.statusText}));T(`Failed: ${t.error}`),P(!0)}}catch(e){T(`Failed: ${e.message}`),P(!0)}}},"Set")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Anonymous access"),n.createElement("input",{type:"checkbox",style:l.checkbox,checked:u,onChange:e=>p(e.target.checked)}),n.createElement("span",{style:l.hint},"view dashboards without login")),n.createElement("div",{style:l.sectionTitle},"Data Sources"),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Signal K URL override"),n.createElement("input",{style:l.input,placeholder:`auto: host.containers.internal:${window.location.port||"3000"}`,value:v,onChange:e=>E(e.target.value)}),n.createElement("span",{style:l.hint},"auto-detected, only set to override")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"QuestDB container name"),n.createElement("input",{style:l.input,value:f,onChange:e=>g(e.target.value)})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"PostgreSQL port"),n.createElement("input",{style:l.inputSmall,type:"number",value:m,onChange:e=>y(Number(e.target.value))})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Network name"),n.createElement("input",{style:l.input,value:b,onChange:e=>h(e.target.value)}),n.createElement("span",{style:l.hint},"shared network for container DNS")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Bind to 0.0.0.0"),n.createElement("input",{type:"checkbox",style:l.checkbox,checked:k,onChange:e=>S(e.target.checked)}),n.createElement("span",{style:{...l.hint,color:k?"#ef4444":void 0}},k?"Caution! This can expose Grafana to the internet":"Only needed for remote access outside localhost")),R&&n.createElement("div",{style:{...l.status,color:z?"#ef4444":"#10b981",marginTop:16}},R),n.createElement("div",{style:{marginTop:24}},n.createElement("button",{style:{...l.btn,...l.btnSave},onClick:()=>{t({grafanaPort:r,grafanaVersion:i,adminPassword:c,anonymousAccess:u,questdbContainerName:f,questdbPgPort:m,signalkUrl:v,networkName:b,bindToAllInterfaces:k}),T("Saved! Plugin will restart with new configuration."),P(!1)}},"Save Configuration")))}}}]);
|
package/public/main.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{var e,r,t={316(){}},a={};function n(e){var r=a[e];if(void 0!==r)return r.exports;var o=a[e]={exports:{}};return t[e](o,o.exports,n),o.exports}n.m=t,n.c=a,n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((r,t)=>(n.f[t](e,r),r),[])),n.u=e=>e+".js",n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-grafana:",n.l=(t,a,o,i)=>{if(e[t])e[t].push(a);else{var l,s;if(void 0!==o)for(var c=document.getElementsByTagName("script"),u=0;u<c.length;u++){var p=c[u];if(p.getAttribute("src")==t||p.getAttribute("data-webpack")==r+o){l=p;break}}l||(s=!0,(l=document.createElement("script")).charset="utf-8",n.nc&&l.setAttribute("nonce",n.nc),l.setAttribute("data-webpack",r+o),l.src=t),e[t]=[a];var f=(r,a)=>{l.onerror=l.onload=null,clearTimeout(d);var n=e[t];if(delete e[t],l.parentNode&&l.parentNode.removeChild(l),n&&n.forEach(e=>e(a)),r)return r(a)},d=setTimeout(f.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=f.bind(null,l.onerror),l.onload=f.bind(null,l.onload),s&&document.head.appendChild(l)}},(()=>{n.S={};var e={},r={};n.I=(t,a)=>{a||(a=[]);var o=r[t];if(o||(o=r[t]={}),!(a.indexOf(o)>=0)){if(a.push(o),e[t])return e[t];n.o(n.S,t)||(n.S[t]={});var i=n.S[t],l="signalk-grafana",s=[];return"default"===t&&((e,r,t,a)=>{var o=i[e]=i[e]||{},s=o[r];(!s||!s.loaded&&(1!=!s.eager?a:l>s.from))&&(o[r]={get:()=>n.e(540).then(()=>()=>n(540)),from:l,eager:!1})})("react","19.2.4"),e[t]=s.length?Promise.all(s).then(()=>e[t]=1):1}}})(),(()=>{var e;n.g.importScripts&&(e=n.g.location+"");var r=n.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var a=t.length-1;a>-1&&(!e||!/^http(s?):/.test(e));)e=t[a--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),n.p=e})(),(()=>{var e={792:0};n.f.j=(r,t)=>{var a=n.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else{var o=new Promise((t,n)=>a=e[r]=[t,n]);t.push(a[2]=o);var i=n.p+n.u(r),l=new Error;n.l(i,t=>{if(n.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",l.name="ChunkLoadError",l.type=o,l.request=i,a[1](l)}},"chunk-"+r,r)}};var r=(r,t)=>{var a,o,[i,l,s]=t,c=0;if(i.some(r=>0!==e[r])){for(a in l)n.o(l,a)&&(n.m[a]=l[a]);s&&s(n)}for(r&&r(t);c<i.length;c++)o=i[c],n.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=self.webpackChunksignalk_grafana=self.webpackChunksignalk_grafana||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),n(316)})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var signalk_grafana;(()=>{"use strict";var e,r,t,n,a,o,i,u,l,s,f,p,c,d,h,g,v,m,b,y={623(e,r,t){var n={"./PluginConfigurationPanel":()=>t.e(805).then(()=>()=>t(805))},a=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),o=(e,r)=>{if(t.S){var n="default",a=t.S[n];if(a&&a!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>a,init:()=>o})}},w={};function S(e){var r=w[e];if(void 0!==r)return r.exports;var t=w[e]={exports:{}};return y[e](t,t.exports,S),t.exports}S.m=y,S.c=w,S.d=(e,r)=>{for(var t in r)S.o(r,t)&&!S.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},S.f={},S.e=e=>Promise.all(Object.keys(S.f).reduce((r,t)=>(S.f[t](e,r),r),[])),S.u=e=>e+".js",S.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),S.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-grafana:",S.l=(t,n,a,o)=>{if(e[t])e[t].push(n);else{var i,u;if(void 0!==a)for(var l=document.getElementsByTagName("script"),s=0;s<l.length;s++){var f=l[s];if(f.getAttribute("src")==t||f.getAttribute("data-webpack")==r+a){i=f;break}}i||(u=!0,(i=document.createElement("script")).charset="utf-8",S.nc&&i.setAttribute("nonce",S.nc),i.setAttribute("data-webpack",r+a),i.src=t),e[t]=[n];var p=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var a=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),a&&a.forEach(e=>e(n)),r)return r(n)},c=setTimeout(p.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=p.bind(null,i.onerror),i.onload=p.bind(null,i.onload),u&&document.head.appendChild(i)}},S.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{S.S={};var e={},r={};S.I=(t,n)=>{n||(n=[]);var a=r[t];if(a||(a=r[t]={}),!(n.indexOf(a)>=0)){if(n.push(a),e[t])return e[t];S.o(S.S,t)||(S.S[t]={});var o=S.S[t],i="signalk-grafana",u=[];return"default"===t&&((e,r,t,n)=>{var a=o[e]=o[e]||{},u=a[r];(!u||!u.loaded&&(1!=!u.eager?n:i>u.from))&&(a[r]={get:()=>S.e(540).then(()=>()=>S(540)),from:i,eager:!1})})("react","19.2.4"),e[t]=u.length?Promise.all(u).then(()=>e[t]=1):1}}})(),(()=>{var e;S.g.importScripts&&(e=S.g.location+"");var r=S.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),S.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var a=e[n],o=(typeof a)[0];if(n>=r.length)return"u"==o;var i=r[n],u=(typeof i)[0];if(o!=u)return"o"==o&&"n"==u||"s"==u||"u"==o;if("o"!=o&&"u"!=o&&a!=i)return a<i;n++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(u=e[o]))[0]?"-":(n>0?".":"")+(n=2,u);return t}var i=[];for(o=1;o<e.length;o++){var u=e[o];i.push(0===u?"not("+l()+")":1===u?"("+l()+" || "+l()+")":2===u?i.pop()+" "+i.pop():a(u))}return l();function l(){return i.pop().replace(/^\((.+)\)$/,"$1")}},o=(e,r)=>{if(0 in e){r=t(r);var n=e[0],a=n<0;a&&(n=-n-1);for(var i=0,u=1,l=!0;;u++,i++){var s,f,p=u<e.length?(typeof e[u])[0]:"";if(i>=r.length||"o"==(f=(typeof(s=r[i]))[0]))return!l||("u"==p?u>n&&!a:""==p!=a);if("u"==f){if(!l||"u"!=p)return!1}else if(l)if(p==f)if(u<=n){if(s!=e[u])return!1}else{if(a?s>e[u]:s<e[u])return!1;s!=e[u]&&(l=!1)}else if("s"!=p&&"n"!=p){if(a||u<=n)return!1;l=!1,u--}else{if(u<=n||f<p!=a)return!1;l=!1}else"s"!=p&&"n"!=p&&(l=!1,u--)}}var c=[],d=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?d()|d():2==h?d()&d():h?o(h,r):!d())}return!!d()},i=(e,r)=>e&&S.o(e,r),u=e=>(e.loaded=1,e.get()),l=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),s=(e,r,t)=>{var a=t?l(e[r]):e[r];return Object.keys(a).reduce((e,r)=>!e||!a[e].loaded&&n(e,r)?r:e,0)},f=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",p=e=>{throw new Error(e)},c=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},d=(e,r,t)=>t?t():((e,r)=>p("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),h=(e=>function(r,t,n,a,o){var i=S.I(r);return i&&i.then&&!n?i.then(e.bind(e,r,S.S[r],t,!1,a,o)):e(r,S.S[r],t,n,a,o)})((e,r,t,n,a,l)=>{if(!i(r,t))return d(e,t,l);var p=s(r,t,n);return o(a,p)||c(f(r,t,p,a)),u(r[t][p])}),g={},v={231:()=>h("default","react",!1,[1,19],()=>S.e(540).then(()=>()=>S(540)))},m={805:[231]},b={},S.f.consumes=(e,r)=>{S.o(m,e)&&m[e].forEach(e=>{if(S.o(g,e))return r.push(g[e]);if(!b[e]){var t=r=>{g[e]=0,S.m[e]=t=>{delete S.c[e],t.exports=r()}};b[e]=!0;var n=r=>{delete g[e],S.m[e]=t=>{throw delete S.c[e],r}};try{var a=v[e]();a.then?r.push(g[e]=a.then(t).catch(n)):t(a)}catch(e){n(e)}}})},(()=>{var e={655:0};S.f.j=(r,t)=>{var n=S.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var a=new Promise((t,a)=>n=e[r]=[t,a]);t.push(n[2]=a);var o=S.p+S.u(r),i=new Error;S.l(o,t=>{if(S.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var a=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+a+": "+o+")",i.name="ChunkLoadError",i.type=a,i.request=o,n[1](i)}},"chunk-"+r,r)}};var r=(r,t)=>{var n,a,[o,i,u]=t,l=0;if(o.some(r=>0!==e[r])){for(n in i)S.o(i,n)&&(S.m[n]=i[n]);u&&u(S)}for(r&&r(t);l<o.length;l++)a=o[l],S.o(e,a)&&e[a]&&e[a][0](),e[a]=0},t=self.webpackChunksignalk_grafana=self.webpackChunksignalk_grafana||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var k=S(623);signalk_grafana=k})();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { ModuleFederationPlugin } = require("webpack").container;
|
|
3
|
+
const packageJson = require("./package.json");
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
entry: "./src/configpanel/index",
|
|
7
|
+
mode: "production",
|
|
8
|
+
output: {
|
|
9
|
+
path: path.resolve(__dirname, "public"),
|
|
10
|
+
clean: false,
|
|
11
|
+
},
|
|
12
|
+
module: {
|
|
13
|
+
rules: [
|
|
14
|
+
{
|
|
15
|
+
test: /\.jsx?$/,
|
|
16
|
+
loader: "babel-loader",
|
|
17
|
+
exclude: /node_modules/,
|
|
18
|
+
options: { presets: ["@babel/preset-react"] },
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
resolve: {
|
|
23
|
+
extensions: [".js", ".jsx"],
|
|
24
|
+
},
|
|
25
|
+
plugins: [
|
|
26
|
+
new ModuleFederationPlugin({
|
|
27
|
+
name: packageJson.name.replace(/[-@/]/g, "_"),
|
|
28
|
+
library: {
|
|
29
|
+
type: "var",
|
|
30
|
+
name: packageJson.name.replace(/[-@/]/g, "_"),
|
|
31
|
+
},
|
|
32
|
+
filename: "remoteEntry.js",
|
|
33
|
+
exposes: {
|
|
34
|
+
"./PluginConfigurationPanel":
|
|
35
|
+
"./src/configpanel/PluginConfigurationPanel",
|
|
36
|
+
},
|
|
37
|
+
shared: {
|
|
38
|
+
react: { singleton: true, requiredVersion: "^19" },
|
|
39
|
+
"react-dom": { singleton: true, requiredVersion: "^19" },
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
};
|