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.
@@ -0,0 +1,2 @@
1
+ dist/
2
+ node_modules/
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"}
@@ -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,2 @@
1
+ import { Config } from "./config/schema";
2
+ export declare function generateProvisioning(dataDir: string, config: Config): void;
@@ -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)}}]);
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license React
3
+ * react.production.js
4
+ *
5
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */
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
+ };