iobroker.beszel 0.1.2
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/.github/auto-merge.yml +2 -0
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/automerge-dependabot.yml +32 -0
- package/.github/workflows/test-and-release.yml +62 -0
- package/.vscode/settings.json +12 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +91 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/admin/beszel.svg +9 -0
- package/admin/i18n/de/translations.json +43 -0
- package/admin/i18n/en/translations.json +43 -0
- package/admin/i18n/es/translations.json +43 -0
- package/admin/i18n/fr/translations.json +43 -0
- package/admin/i18n/it/translations.json +43 -0
- package/admin/i18n/nl/translations.json +43 -0
- package/admin/i18n/pl/translations.json +43 -0
- package/admin/i18n/pt/translations.json +43 -0
- package/admin/i18n/ru/translations.json +43 -0
- package/admin/i18n/uk/translations.json +43 -0
- package/admin/i18n/zh-cn/translations.json +43 -0
- package/admin/jsonConfig.json +240 -0
- package/build/lib/beszel-client.d.ts +39 -0
- package/build/lib/beszel-client.d.ts.map +1 -0
- package/build/lib/beszel-client.js +199 -0
- package/build/lib/state-manager.d.ts +47 -0
- package/build/lib/state-manager.d.ts.map +1 -0
- package/build/lib/state-manager.js +738 -0
- package/build/lib/types.d.ts +174 -0
- package/build/lib/types.d.ts.map +1 -0
- package/build/lib/types.js +2 -0
- package/build/main.d.ts +2 -0
- package/build/main.d.ts.map +1 -0
- package/build/main.js +191 -0
- package/eslint.config.mjs +36 -0
- package/io-package.json +162 -0
- package/package.json +61 -0
- package/scripts/version.js +28 -0
- package/src/lib/beszel-client.ts +216 -0
- package/src/lib/state-manager.ts +1050 -0
- package/src/lib/types.ts +192 -0
- package/src/main.ts +199 -0
- package/test/testPackageFiles.ts +5 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +24 -0
- package/tsconfig.test.json +9 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.BeszelClient = void 0;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const url_1 = require("url");
|
|
40
|
+
const TOKEN_REFRESH_MS = 23 * 60 * 60 * 1000; // 23 hours
|
|
41
|
+
/**
|
|
42
|
+
* HTTP client for the Beszel PocketBase REST API.
|
|
43
|
+
* Uses only Node.js built-in http/https — no extra dependencies.
|
|
44
|
+
*/
|
|
45
|
+
class BeszelClient {
|
|
46
|
+
baseUrl;
|
|
47
|
+
username;
|
|
48
|
+
password;
|
|
49
|
+
token = null;
|
|
50
|
+
tokenTime = 0;
|
|
51
|
+
constructor(url, username, password) {
|
|
52
|
+
// Strip trailing slash
|
|
53
|
+
this.baseUrl = url.replace(/\/+$/, "");
|
|
54
|
+
this.username = username;
|
|
55
|
+
this.password = password;
|
|
56
|
+
}
|
|
57
|
+
/** Force token re-authentication on the next request */
|
|
58
|
+
invalidateToken() {
|
|
59
|
+
this.token = null;
|
|
60
|
+
this.tokenTime = 0;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Test the connection to Beszel.
|
|
64
|
+
* Returns { success: true } or { success: false, message: reason }.
|
|
65
|
+
*/
|
|
66
|
+
async checkConnection() {
|
|
67
|
+
try {
|
|
68
|
+
this.invalidateToken();
|
|
69
|
+
await this.authenticate();
|
|
70
|
+
return { success: true, message: "Connected successfully" };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
message: err instanceof Error ? err.message : String(err),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Fetch all systems */
|
|
80
|
+
async getSystems() {
|
|
81
|
+
await this.ensureToken();
|
|
82
|
+
const data = await this.fetchJson("/api/collections/systems/records?perPage=200&sort=name");
|
|
83
|
+
return data.items;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the latest 1m stats per system.
|
|
87
|
+
* Returns a Map<systemId, SystemStats>.
|
|
88
|
+
*
|
|
89
|
+
* @param systemIds
|
|
90
|
+
*/
|
|
91
|
+
async getLatestStats(systemIds) {
|
|
92
|
+
if (systemIds.length === 0) {
|
|
93
|
+
return new Map();
|
|
94
|
+
}
|
|
95
|
+
await this.ensureToken();
|
|
96
|
+
const data = await this.fetchJson("/api/collections/system_stats/records?sort=-updated&perPage=200&filter=type%3D'1m'");
|
|
97
|
+
// Deduplicate: keep the newest record per system
|
|
98
|
+
const result = new Map();
|
|
99
|
+
for (const record of data.items) {
|
|
100
|
+
if (!result.has(record.system)) {
|
|
101
|
+
result.set(record.system, record.stats);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
/** Fetch all containers */
|
|
107
|
+
async getContainers() {
|
|
108
|
+
await this.ensureToken();
|
|
109
|
+
const data = await this.fetchJson("/api/collections/containers/records?perPage=500&sort=system%2Cname");
|
|
110
|
+
return data.items;
|
|
111
|
+
}
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
// Private helpers
|
|
114
|
+
// -------------------------------------------------------------------------
|
|
115
|
+
async ensureToken() {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
if (this.token && now - this.tokenTime < TOKEN_REFRESH_MS) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await this.authenticate();
|
|
121
|
+
}
|
|
122
|
+
async authenticate() {
|
|
123
|
+
const body = JSON.stringify({
|
|
124
|
+
identity: this.username,
|
|
125
|
+
password: this.password,
|
|
126
|
+
});
|
|
127
|
+
const data = await this.request("POST", "/api/collections/users/auth-with-password", body, null);
|
|
128
|
+
this.token = data.token;
|
|
129
|
+
this.tokenTime = Date.now();
|
|
130
|
+
}
|
|
131
|
+
async fetchJson(path) {
|
|
132
|
+
return this.request("GET", path, null, this.token);
|
|
133
|
+
}
|
|
134
|
+
request(method, path, body, token) {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
let parsedUrl;
|
|
137
|
+
try {
|
|
138
|
+
parsedUrl = new url_1.URL(this.baseUrl + path);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
reject(new Error(`Invalid URL: ${this.baseUrl + path}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
145
|
+
const transport = isHttps ? https : http;
|
|
146
|
+
const headers = {
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
Accept: "application/json",
|
|
149
|
+
};
|
|
150
|
+
if (token) {
|
|
151
|
+
headers.Authorization = token;
|
|
152
|
+
}
|
|
153
|
+
if (body !== null) {
|
|
154
|
+
headers["Content-Length"] = Buffer.byteLength(body).toString();
|
|
155
|
+
}
|
|
156
|
+
const options = {
|
|
157
|
+
hostname: parsedUrl.hostname,
|
|
158
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
159
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
160
|
+
method,
|
|
161
|
+
headers,
|
|
162
|
+
timeout: 15000,
|
|
163
|
+
};
|
|
164
|
+
const req = transport.request(options, (res) => {
|
|
165
|
+
const chunks = [];
|
|
166
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
167
|
+
res.on("end", () => {
|
|
168
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
169
|
+
if (!res.statusCode ||
|
|
170
|
+
res.statusCode < 200 ||
|
|
171
|
+
res.statusCode >= 300) {
|
|
172
|
+
// Propagate 401 specifically so caller can re-auth
|
|
173
|
+
const err = new Error(`HTTP ${res.statusCode ?? "?"}: ${raw.slice(0, 200)}`);
|
|
174
|
+
err.code =
|
|
175
|
+
res.statusCode === 401 ? "UNAUTHORIZED" : "HTTP_ERROR";
|
|
176
|
+
reject(err);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
resolve(JSON.parse(raw));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
reject(new Error(`Invalid JSON response from ${path}`));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
req.on("timeout", () => {
|
|
188
|
+
req.destroy();
|
|
189
|
+
reject(new Error(`Request to ${path} timed out`));
|
|
190
|
+
});
|
|
191
|
+
req.on("error", (err) => reject(err));
|
|
192
|
+
if (body !== null) {
|
|
193
|
+
req.write(body);
|
|
194
|
+
}
|
|
195
|
+
req.end();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.BeszelClient = BeszelClient;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type * as utils from "@iobroker/adapter-core";
|
|
2
|
+
import type { AdapterConfig, BeszelContainer, BeszelSystem, SystemStats } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Manages creation and updates of ioBroker states for Beszel systems.
|
|
5
|
+
*/
|
|
6
|
+
export declare class StateManager {
|
|
7
|
+
private readonly adapter;
|
|
8
|
+
constructor(adapter: utils.AdapterInstance);
|
|
9
|
+
/**
|
|
10
|
+
* Sanitize a name to a valid ioBroker state ID segment.
|
|
11
|
+
* Lowercase, replace non-alphanumeric with _, max 50 chars, trim underscores.
|
|
12
|
+
*
|
|
13
|
+
* @param name
|
|
14
|
+
*/
|
|
15
|
+
sanitize(name: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Update all states for a single system.
|
|
18
|
+
*
|
|
19
|
+
* @param system
|
|
20
|
+
* @param stats
|
|
21
|
+
* @param containers
|
|
22
|
+
* @param config
|
|
23
|
+
*/
|
|
24
|
+
updateSystem(system: BeszelSystem, stats: SystemStats | undefined, containers: BeszelContainer[], config: AdapterConfig): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Remove device objects for systems that are no longer in Beszel.
|
|
27
|
+
*
|
|
28
|
+
* @param activeSystemNames
|
|
29
|
+
*/
|
|
30
|
+
cleanupSystems(activeSystemNames: string[]): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete states for metrics that have been disabled in the config.
|
|
33
|
+
* Called on startup to clean up previously-enabled states.
|
|
34
|
+
*
|
|
35
|
+
* @param systemId
|
|
36
|
+
* @param config
|
|
37
|
+
*/
|
|
38
|
+
cleanupMetrics(systemId: string, config: AdapterConfig): Promise<void>;
|
|
39
|
+
private updateStatsStates;
|
|
40
|
+
private updateContainers;
|
|
41
|
+
private ensureChannel;
|
|
42
|
+
private deleteChannelIfExists;
|
|
43
|
+
private createAndSetState;
|
|
44
|
+
private computeTopAvgTemp;
|
|
45
|
+
private formatUptime;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=state-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../../src/lib/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAKpB;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;gBAEpC,OAAO,EAAE,KAAK,CAAC,eAAe;IAI1C;;;;;OAKG;IACI,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQrC;;;;;;;OAOG;IACU,YAAY,CACvB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC;IAgKhB;;;;OAIG;IACU,cAAc,CAAC,iBAAiB,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCvE;;;;;;OAMG;IACU,cAAc,CACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC;YA2FF,iBAAiB;YAsiBjB,gBAAgB;YAqFhB,aAAa;YAQb,qBAAqB;YAWrB,iBAAiB;IAa/B,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,YAAY;CAgBrB"}
|