@aurora-blue/signalk-connector 0.2.0-dev.20
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/LICENSE +21 -0
- package/README.md +122 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/VERSION +1 -0
- package/dist/src/backoff.d.ts +1 -0
- package/dist/src/backoff.js +9 -0
- package/dist/src/config.d.ts +192 -0
- package/dist/src/config.js +317 -0
- package/dist/src/connector.d.ts +69 -0
- package/dist/src/connector.js +487 -0
- package/dist/src/delta.d.ts +4 -0
- package/dist/src/delta.js +137 -0
- package/dist/src/geometry.d.ts +3 -0
- package/dist/src/geometry.js +25 -0
- package/dist/src/http-client.d.ts +22 -0
- package/dist/src/http-client.js +158 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +93 -0
- package/dist/src/safety/anchor-alarm.d.ts +33 -0
- package/dist/src/safety/anchor-alarm.js +79 -0
- package/dist/src/state-store.d.ts +45 -0
- package/dist/src/state-store.js +491 -0
- package/dist/src/storage-budget.d.ts +35 -0
- package/dist/src/storage-budget.js +88 -0
- package/dist/src/telemetry-catalog.d.ts +19 -0
- package/dist/src/telemetry-catalog.js +93 -0
- package/dist/src/telemetry-reducer.d.ts +22 -0
- package/dist/src/telemetry-reducer.js +150 -0
- package/dist/src/types.d.ts +115 -0
- package/dist/src/types.js +5 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +4 -0
- package/package.json +60 -0
- package/public/aurora-blue-aboard-mark.svg +19 -0
- package/public/aurora-blue-archipelago-dark.svg +32 -0
- package/public/aurora-blue-archipelago-light.svg +81 -0
- package/public/ci-brand.css +77 -0
- package/public/ci-fonts.css +28 -0
- package/public/ci-tokens.css +139 -0
- package/public/favicon.svg +19 -0
- package/public/fonts/LICENSE +96 -0
- package/public/fonts/ubuntu-latin-300-normal.woff2 +0 -0
- package/public/fonts/ubuntu-latin-400-normal.woff2 +0 -0
- package/public/fonts/ubuntu-latin-500-normal.woff2 +0 -0
- package/public/fonts/ubuntu-latin-700-normal.woff2 +0 -0
- package/public/index.html +177 -0
- package/public/locales/de.json +82 -0
- package/public/locales/en.json +82 -0
- package/public/locales/es.json +82 -0
- package/public/locales/fr.json +82 -0
- package/public/status.css +262 -0
- package/public/status.js +237 -0
package/public/status.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const endpoint = "/plugins/aurora-blue-signalk-connector/status";
|
|
2
|
+
const supportedLocales = ["en", "de", "fr", "es"];
|
|
3
|
+
const browserLocale =
|
|
4
|
+
(globalThis.navigator?.languages ?? [globalThis.navigator?.language ?? "en"])
|
|
5
|
+
.map((value) => value.toLowerCase().split("-")[0])
|
|
6
|
+
.find((value) => supportedLocales.includes(value)) ?? "en";
|
|
7
|
+
let messages = {
|
|
8
|
+
"appearance.system": "System appearance: {appearance}",
|
|
9
|
+
"appearance.dark": "dark",
|
|
10
|
+
"appearance.light": "light",
|
|
11
|
+
"error.load_failed": "Connector status could not be loaded.",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const t = (key, parameters = {}) => {
|
|
15
|
+
let message = messages[key] ?? key;
|
|
16
|
+
Object.entries(parameters).forEach(([name, value]) => {
|
|
17
|
+
message = message.replaceAll(`{${name}}`, String(value));
|
|
18
|
+
});
|
|
19
|
+
return message;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const element = (id) => document.getElementById(id);
|
|
23
|
+
const number = new Intl.NumberFormat(browserLocale);
|
|
24
|
+
const decimalNumber = new Intl.NumberFormat(browserLocale, {
|
|
25
|
+
minimumFractionDigits: 1,
|
|
26
|
+
maximumFractionDigits: 1,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const setText = (id, value) => {
|
|
30
|
+
element(id).textContent = value;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const translatedValue = (group, value) => {
|
|
34
|
+
const key = `value.${group}.${String(value).replaceAll("-", "_")}`;
|
|
35
|
+
return messages[key] ?? String(value);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Follow the browser's system preference, not the Signal K host's operating system.
|
|
39
|
+
const systemAppearance = window.matchMedia("(prefers-color-scheme: dark)");
|
|
40
|
+
const renderAppearance = () => {
|
|
41
|
+
setText(
|
|
42
|
+
"appearance",
|
|
43
|
+
t("appearance.system", {
|
|
44
|
+
appearance: t(
|
|
45
|
+
systemAppearance.matches ? "appearance.dark" : "appearance.light",
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
renderAppearance();
|
|
51
|
+
systemAppearance.addEventListener("change", renderAppearance);
|
|
52
|
+
|
|
53
|
+
const setBadge = (id, value, tone) => {
|
|
54
|
+
const badge = element(id);
|
|
55
|
+
badge.textContent = value;
|
|
56
|
+
badge.className = `badge ${tone}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const formatBytes = (bytes) => {
|
|
60
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
61
|
+
if (bytes < 1024 * 1024) return `${decimalNumber.format(bytes / 1024)} KiB`;
|
|
62
|
+
return `${decimalNumber.format(bytes / (1024 * 1024))} MiB`;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const formatTimestamp = (value) =>
|
|
66
|
+
value === null
|
|
67
|
+
? t("status.never")
|
|
68
|
+
: new Date(value).toLocaleString(browserLocale);
|
|
69
|
+
|
|
70
|
+
const toneForLifecycle = (value) => {
|
|
71
|
+
if (value === "running") return "good";
|
|
72
|
+
if (value === "error") return "bad";
|
|
73
|
+
if (value === "starting") return "warn";
|
|
74
|
+
return "neutral";
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const toneForMode = (value) => {
|
|
78
|
+
if (value === "active") return "good";
|
|
79
|
+
if (value === "standby") return "warn";
|
|
80
|
+
if (value === "disabled") return "neutral";
|
|
81
|
+
return "bad";
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const render = (payload) => {
|
|
85
|
+
setBadge(
|
|
86
|
+
"lifecycle",
|
|
87
|
+
translatedValue("lifecycle", payload.lifecycle),
|
|
88
|
+
toneForLifecycle(payload.lifecycle),
|
|
89
|
+
);
|
|
90
|
+
setText("version", payload.connector_version);
|
|
91
|
+
setText("brand-version", payload.connector_version);
|
|
92
|
+
|
|
93
|
+
const notice = element("notice");
|
|
94
|
+
if (payload.last_error === null) {
|
|
95
|
+
notice.classList.add("hidden");
|
|
96
|
+
} else {
|
|
97
|
+
const errorKey =
|
|
98
|
+
{
|
|
99
|
+
"Connector startup failed.": "error.startup_failed",
|
|
100
|
+
"Connector configuration is invalid.": "error.configuration_invalid",
|
|
101
|
+
}[payload.last_error.message] ?? "error.connector_reported";
|
|
102
|
+
notice.textContent = `${t(errorKey)} (${formatTimestamp(payload.last_error.occurred_at)})`;
|
|
103
|
+
notice.classList.remove("hidden");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const status = payload.status;
|
|
107
|
+
if (status === null) {
|
|
108
|
+
setBadge("mode", t("status.mode_unavailable"), "neutral");
|
|
109
|
+
setText("pairing", t("status.unavailable"));
|
|
110
|
+
setText(
|
|
111
|
+
"updated",
|
|
112
|
+
t("status.updated", { time: formatTimestamp(payload.generated_at) }),
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setBadge(
|
|
118
|
+
"mode",
|
|
119
|
+
translatedValue("mode", status.operatingMode),
|
|
120
|
+
toneForMode(status.operatingMode),
|
|
121
|
+
);
|
|
122
|
+
setText("pairing", t(status.paired ? "status.paired" : "status.unpaired"));
|
|
123
|
+
setText(
|
|
124
|
+
"configuration-state",
|
|
125
|
+
t("status.runtime_configuration", {
|
|
126
|
+
state: translatedValue("configuration", status.runtimeConfigurationState),
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
setText("transfer", translatedValue("transfer", status.transferState));
|
|
130
|
+
const delayed = status.transferMessage?.match(
|
|
131
|
+
/^Transfer delayed \((HTTP (\d+)|target unavailable)\) - retry in (\d+)s$/,
|
|
132
|
+
);
|
|
133
|
+
const transferDetail =
|
|
134
|
+
status.transferMessage === null
|
|
135
|
+
? t("status.last_success", {
|
|
136
|
+
time: formatTimestamp(status.lastSuccessAt),
|
|
137
|
+
})
|
|
138
|
+
: delayed === null || delayed === undefined
|
|
139
|
+
? t("status.transfer_delayed_generic")
|
|
140
|
+
: t("status.transfer_delayed", {
|
|
141
|
+
reason:
|
|
142
|
+
delayed[2] === undefined
|
|
143
|
+
? t("status.target_unavailable")
|
|
144
|
+
: t("status.http_error", { status: delayed[2] }),
|
|
145
|
+
seconds: delayed[3],
|
|
146
|
+
});
|
|
147
|
+
setText("last-transfer", transferDetail);
|
|
148
|
+
setText(
|
|
149
|
+
"buffer",
|
|
150
|
+
t("status.events", {
|
|
151
|
+
current: number.format(status.bufferedEvents),
|
|
152
|
+
limit: number.format(status.bufferEventLimit),
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
setText(
|
|
156
|
+
"buffer-bytes",
|
|
157
|
+
`${formatBytes(status.bufferedBytes)} / ${formatBytes(status.bufferByteLimit)}`,
|
|
158
|
+
);
|
|
159
|
+
setText(
|
|
160
|
+
"pressure",
|
|
161
|
+
`${translatedValue("pressure", status.bufferPressure)} (${decimalNumber.format(status.bufferUtilization * 100)}%)`,
|
|
162
|
+
);
|
|
163
|
+
setText(
|
|
164
|
+
"remaining",
|
|
165
|
+
status.estimatedRemainingBufferHours === null
|
|
166
|
+
? t("status.estimating_retention")
|
|
167
|
+
: t("status.hours_remaining", {
|
|
168
|
+
hours: decimalNumber.format(status.estimatedRemainingBufferHours),
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
setText("accepted", number.format(status.acceptedThrough));
|
|
172
|
+
setText("suppressed", number.format(status.suppressedEvents));
|
|
173
|
+
setText("dropped", number.format(status.droppedEvents));
|
|
174
|
+
setText(
|
|
175
|
+
"storage",
|
|
176
|
+
`${translatedValue("storage", status.storageProfile)} (${translatedValue("storage", status.resolvedStorageProfile)})`,
|
|
177
|
+
);
|
|
178
|
+
setText("anchor", translatedValue("anchor", status.anchorAlarmState));
|
|
179
|
+
setText(
|
|
180
|
+
"updated",
|
|
181
|
+
t("status.updated", { time: formatTimestamp(payload.generated_at) }),
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const renderFailure = (message) => {
|
|
186
|
+
setBadge("lifecycle", t("status.unavailable"), "bad");
|
|
187
|
+
const notice = element("notice");
|
|
188
|
+
notice.textContent = message;
|
|
189
|
+
notice.classList.remove("hidden");
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const refresh = async () => {
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetch(endpoint, {
|
|
195
|
+
headers: { Accept: "application/json" },
|
|
196
|
+
cache: "no-store",
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
renderFailure(
|
|
201
|
+
response.status === 401 || response.status === 403
|
|
202
|
+
? t("error.admin_required")
|
|
203
|
+
: t("error.load_failed"),
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
render(await response.json());
|
|
209
|
+
} catch {
|
|
210
|
+
renderFailure(t("error.load_failed"));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const initialize = async () => {
|
|
215
|
+
const english = await fetch("locales/en.json")
|
|
216
|
+
.then((response) => response.json())
|
|
217
|
+
.catch(() => messages);
|
|
218
|
+
const selected =
|
|
219
|
+
browserLocale === "en"
|
|
220
|
+
? {}
|
|
221
|
+
: await fetch(`locales/${browserLocale}.json`)
|
|
222
|
+
.then((response) => response.json())
|
|
223
|
+
.catch(() => ({}));
|
|
224
|
+
messages = { ...english, ...selected };
|
|
225
|
+
document.documentElement.lang = browserLocale;
|
|
226
|
+
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
|
227
|
+
node.textContent = t(node.dataset.i18n);
|
|
228
|
+
});
|
|
229
|
+
document.querySelectorAll("[data-i18n-aria-label]").forEach((node) => {
|
|
230
|
+
node.setAttribute("aria-label", t(node.dataset.i18nAriaLabel));
|
|
231
|
+
});
|
|
232
|
+
renderAppearance();
|
|
233
|
+
await refresh();
|
|
234
|
+
setInterval(() => void refresh(), 5000);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
void initialize();
|