Skip to content

Commit dbd1caa

Browse files
[types]: Added types for Notifications on Adapter/Instances (#2874)
* Extend types * Update objects.d.ts * Fixed tests * Fixed linter * correctly infer getObjectView id - unwrap callback on touched code - simplify types where possible - remove types were not needed and not yet correct * reuse type and do not force type casting where it would be cheating --------- Co-authored-by: foxriver76 <[email protected]>
1 parent 3718997 commit dbd1caa

File tree

5 files changed

+100
-53
lines changed

5 files changed

+100
-53
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ The definition also contains localized names and descriptions that can be used t
424424
Registered notifications are stored per host and can be requested via messages to the host. They are also stored when the controller stops and loaded on next start.
425425
Additionally, a summary of the stored categories per scope with a number of stored notifications and the last added timestamp is available in the state `system.host.hostname.notifications.scopeid` as a JSON.
426426

427-
The js-controller defines in its io-package the system scope together with all details. You can use this as an example and the JSON schema will help you validate your notifications.
427+
The js-controller defines in its io-package the system scope together with all details. You can use this as an example, and the JSON schema will help you validate your notifications.
428428

429429
```json
430430
{
@@ -534,7 +534,7 @@ The message needs to take the following parameters in the message object:
534534
* categoryFilter - category of notifications
535535
* instanceFilter - instance of notifications
536536

537-
All three are optional and can be a string or null/undefined if ommited.
537+
All three are optional and can be a string or null/undefined if omitted.
538538

539539
### Disk space warnings
540540
**Feature status:** New in 6.0.0
@@ -546,7 +546,7 @@ By default, this threshold is 5 % of disk space. Via the state `system.host.<hos
546546
#### Log levels
547547
**Feature status:** stable
548548

549-
The js-controller and each adapter has defined its own log level. By default, `info` is used. The following log levels can be used:
549+
The js-controller and each adapter can define their own log level. By default, `info` is used. The following log levels can be used:
550550
* silly (most logging)
551551
* debug
552552
* info

packages/cli/src/lib/setup/setupRepo.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ export class Repo {
8080
systemRepos?: ioBroker.RepositoryObject
8181
): Promise<null | ioBroker.RepositoryJson> {
8282
if (!repoName) {
83-
const sysConfig = systemConfig || (await this.objects.getObjectAsync('system.config'));
83+
const sysConfig = systemConfig || (await this.objects.getObject('system.config'));
8484
repoName = sysConfig!.common.activeRepo;
8585
}
8686

87-
const oldRepos = systemRepos || (await this.objects.getObjectAsync('system.repositories'));
87+
const oldRepos = systemRepos || (await this.objects.getObject('system.repositories'));
8888
if (!oldRepos?.native.repositories?.[repoName]) {
8989
console.log(`Error: repository "${repoName}" not found in the "system.repositories`);
9090
return null;
@@ -101,7 +101,7 @@ export class Repo {
101101
(urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://'))
102102
) {
103103
try {
104-
hash = await axios({ url: hashUrl, timeout: 10000 });
104+
hash = await axios({ url: hashUrl, timeout: 10_000 });
105105
} catch (e) {
106106
console.error(`Cannot download repository hash file from "${hashUrl}": ${e.message}`);
107107
}
@@ -161,7 +161,7 @@ export class Repo {
161161
if (changed) {
162162
oldRepos.from = `system.host.${tools.getHostName()}.cli`;
163163
oldRepos.ts = Date.now();
164-
await this.objects.setObjectAsync('system.repositories', oldRepos);
164+
await this.objects.setObject('system.repositories', oldRepos);
165165
}
166166

167167
return oldRepos.native.repositories[repoName].json;
@@ -319,8 +319,8 @@ export class Repo {
319319
const listStr = list.join(', ');
320320
for (const row of objs.rows) {
321321
if (row?.value?.type === 'instance') {
322-
await this.states.setStateAsync(`${row.id}.info.updatesNumber`, { val: list.length, ack: true });
323-
await this.states.setStateAsync(`${row.id}.info.updatesList`, { val: listStr, ack: true });
322+
await this.states.setState(`${row.id}.info.updatesNumber`, { val: list.length, ack: true });
323+
await this.states.setState(`${row.id}.info.updatesList`, { val: listStr, ack: true });
324324
}
325325
}
326326
}
@@ -412,7 +412,7 @@ export class Repo {
412412
delete repoObj.native.repositories[repoName];
413413
repoObj.from = `system.host.${tools.getHostName()}.cli`;
414414
repoObj.ts = Date.now();
415-
await this.objects.setObjectAsync('system.repositories', repoObj);
415+
await this.objects.setObject('system.repositories', repoObj);
416416
}
417417
}
418418
}

packages/controller/src/main.ts

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ interface RepoRequester {
137137
callback: ioBroker.MessageCallbackInfo;
138138
}
139139

140+
/** Host information including host id and running version */
141+
type HostInformation = ioBroker.HostCommon & { host: string; runningVersion: string };
142+
140143
const VIS_ADAPTERS = ['vis', 'vis-2'] as const;
141144
const ioPackage = fs.readJSONSync(path.join(tools.getControllerDir(), 'io-package.json'));
142145
const version = ioPackage.common.version;
@@ -1280,7 +1283,6 @@ function cleanAutoSubscribes(instanceID: ioBroker.ObjectIDs.Instance, callback:
12801283
// remove this instance from autoSubscribe
12811284
if (row.value?.common.subscribable) {
12821285
count++;
1283-
// @ts-expect-error https://github.com/ioBroker/ioBroker.js-controller/issues/2089
12841286
cleanAutoSubscribe(instance, row.id, () => !--count && callback && callback());
12851287
}
12861288
}
@@ -1832,7 +1834,7 @@ function initMessageQueue(): void {
18321834
}
18331835

18341836
/**
1835-
* Send a message to other adapter instance
1837+
* Send a message to another adapter instance
18361838
*
18371839
* @param objName - adapter name (hm-rpc) or id like system.host.rpi/system.adapter,hm-rpc
18381840
* @param command
@@ -1891,10 +1893,11 @@ async function sendTo(
18911893
}
18921894

18931895
/**
1896+
* Get the version information from given host
18941897
*
1895-
* @param hostId
1898+
* @param hostId host to get the version information from
18961899
*/
1897-
async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Record<string, any> | null | undefined> {
1900+
async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<HostInformation | null> {
18981901
const state = await states!.getState(`${hostId}.alive`);
18991902
if (state?.val) {
19001903
return new Promise(resolve => {
@@ -1908,6 +1911,7 @@ async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Reco
19081911
if (timeout) {
19091912
clearTimeout(timeout);
19101913
timeout = null;
1914+
// @ts-expect-error sendTo needs to be fixed, because in some cases there is no error and return value is in first arg
19111915
resolve(ioPack);
19121916
}
19131917
});
@@ -1918,6 +1922,9 @@ async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Reco
19181922
}
19191923
}
19201924

1925+
/**
1926+
* Upload all adapters which are currently in `uploadTasks` queue
1927+
*/
19211928
async function startAdapterUpload(): Promise<void> {
19221929
if (!uploadTasks.length) {
19231930
return;
@@ -2211,40 +2218,34 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
22112218
case 'getInstalled':
22122219
if (msg.callback && msg.from) {
22132220
// Get a list of all hosts
2214-
objects!.getObjectView(
2215-
'system',
2216-
'host',
2217-
{
2218-
startkey: 'system.host.',
2219-
endkey: 'system.host.\u9999'
2220-
},
2221-
async (err, doc) => {
2222-
const result: Record<string, any> = tools.getInstalledInfo(version);
2223-
result.hosts = {};
2224-
if (doc?.rows.length) {
2225-
// Read installed versions of all hosts
2226-
for (const row of doc.rows) {
2227-
// If desired a local version, do not ask it, just answer
2228-
if (row.id === hostObjectPrefix) {
2229-
const ioPackCommon = deepClone(ioPackage.common);
2230-
2231-
ioPackCommon.host = hostname;
2232-
ioPackCommon.runningVersion = version;
2233-
result.hosts[hostname] = ioPackCommon;
2234-
} else {
2235-
// @ts-expect-error https://github.com/ioBroker/ioBroker.js-controller/issues/2089
2236-
const ioPack = await getVersionFromHost(row.id);
2237-
if (ioPack) {
2238-
result.hosts[ioPack.host] = ioPack;
2239-
result.hosts[ioPack.host].controller = true;
2240-
}
2241-
}
2221+
const doc = await objects!.getObjectViewAsync('system', 'host', {
2222+
startkey: 'system.host.',
2223+
endkey: 'system.host.\u9999'
2224+
});
2225+
2226+
const installedInfo = tools.getInstalledInfo();
2227+
const hosts: Record<string, HostInformation> = {};
2228+
2229+
if (doc?.rows.length) {
2230+
// Read installed versions of all hosts
2231+
for (const row of doc.rows) {
2232+
// If desired a local version, do not ask it, just answer
2233+
if (row.id === hostObjectPrefix) {
2234+
const ioPackCommon = deepClone(ioPackage.common);
2235+
2236+
ioPackCommon.host = hostname;
2237+
ioPackCommon.runningVersion = version;
2238+
hosts[hostname] = ioPackCommon;
2239+
} else {
2240+
const ioPack = await getVersionFromHost(row.id);
2241+
if (ioPack) {
2242+
hosts[ioPack.host] = ioPack;
22422243
}
22432244
}
2244-
2245-
sendTo(msg.from, msg.command, result, msg.callback);
22462245
}
2247-
);
2246+
}
2247+
2248+
sendTo(msg.from, msg.command, { ...installedInfo, hosts }, msg.callback);
22482249
} else {
22492250
logger.error(`${hostLogPrefix} Invalid request ${msg.command}. "callback" or "from" is null`);
22502251
}
@@ -2270,7 +2271,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
22702271

22712272
case 'getVersion':
22722273
if (msg.callback && msg.from) {
2273-
const ioPackCommon = deepClone(ioPackage.common);
2274+
const ioPackCommon: ioBroker.HostCommon & { host: string; runningVersion: string } = deepClone(
2275+
ioPackage.common
2276+
);
22742277
ioPackCommon.host = hostname;
22752278
ioPackCommon.runningVersion = version;
22762279
sendTo(msg.from, msg.command, ioPackCommon, msg.callback);
@@ -3033,6 +3036,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
30333036
}
30343037
}
30353038

3039+
/**
3040+
* Collect all instances on this host and call `initInstances`
3041+
*/
30363042
async function getInstances(): Promise<void> {
30373043
if (!objects) {
30383044
throw new Error('Objects database not connected');

packages/types-dev/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ declare global {
478478

479479
interface GetObjectViewItem<T extends AnyObject> {
480480
/** The ID of this object */
481-
id: string;
481+
id: T['_id'];
482482
/** A copy of the object from the DB */
483483
value: T;
484484
}

packages/types-dev/objects.d.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,9 @@ declare global {
595595
description?: ioBroker.StringOrTranslated;
596596
};
597597

598+
/** Format for local and global dependencies */
599+
type Depdendencies = { [adapterName: string]: string }[] | string[];
600+
598601
interface AdapterCommon extends ObjectCommon {
599602
/** Custom attributes to be shown in admin in the object browser */
600603
adminColumns?: string | (string | CustomAdminColumn)[];
@@ -632,9 +635,9 @@ declare global {
632635
/** How the adapter will mainly receive its data. Set this together with @see connectionType */
633636
dataSource?: 'poll' | 'push' | 'assumption';
634637
/** A record of ioBroker adapters (including "js-controller") and version ranges which are required for this adapter on the same host. */
635-
dependencies?: Array<Record<string, string>>;
638+
dependencies?: Depdendencies;
636639
/** A record of ioBroker adapters (including "js-controller") and version ranges which are required for this adapter in the whole system. */
637-
globalDependencies?: Array<Record<string, string>>;
640+
globalDependencies?: Depdendencies;
638641
/** Which files outside the README.md have documentation for the adapter */
639642
docs?: Partial<Record<Languages, string | string[]>>;
640643
/** Whether new instances should be enabled by default. *Should* be `false`! */
@@ -822,6 +825,8 @@ declare global {
822825
defaultLogLevel?: LogLevel;
823826
/** Used date format for formatting */
824827
dateFormat: string;
828+
/** This name will be shown in admin's header. Just to identify the whole installation */
829+
siteName?: string;
825830
/** Default acl for new objects */
826831
defaultNewAcl: {
827832
object: number;
@@ -865,9 +870,9 @@ declare global {
865870
}
866871

867872
/**
868-
* ioBroker has built-in protection for specific attributes of objects. If this protection is installed in the object, then the protected attributes of object cannot be changed by the user without valid password.
873+
* ioBroker has built-in protection for specific attributes of objects. If this protection is installed in the object, then the protected attributes of an object cannot be changed by the user without a valid password.
869874
* To protect the properties from change, the special attribute "nonEdit" must be added to the object. This attribute contains the password, which is required to change the object.
870-
* If object does not have "nonEdit" attribute, so the hash will be saved into "nonEdit.passHash". After that if someone will change the object, he must provide the password in "nonEdit.password".
875+
* If an object does not have "nonEdit" attribute, so the hash will be saved into "nonEdit.passHash". After that, if someone changes the object, he must provide the password in "nonEdit.password".
871876
* If the password is correct, the object attributes will be updated. If the password is wrong, the object will not be changed.
872877
* Note, that all properties outside "nonEdit" can be updated without providing the password. Furthermore, do not confuse e.g. "nonEdit.common" with "obj.common" they are not linked in any way.
873878
*/
@@ -1012,6 +1017,8 @@ declare global {
10121017
json: RepositoryJson | null;
10131018
hash?: string;
10141019
time?: string;
1020+
/** If this repository stable */
1021+
stable?: boolean;
10151022
}
10161023

10171024
interface RepositoryObject extends BaseObject {
@@ -1048,13 +1055,47 @@ declare global {
10481055
common?: Partial<InstanceCommon>;
10491056
}
10501057

1051-
/** TODO: To be defined */
1052-
type NotificationCategory = any;
1058+
// it is defined in notificationHandler.ts
1059+
type NotificationCategory = {
1060+
/** The unique category identifier */
1061+
category:
1062+
| 'memIssues'
1063+
| 'fsIoErrors'
1064+
| 'noDiskSpace'
1065+
| 'accessErrors'
1066+
| 'nonExistingFileErrors'
1067+
| 'remoteHostErrors'
1068+
| 'restartLoop'
1069+
| 'fileToJsonl'
1070+
| 'automaticAdapterUpgradeFailed'
1071+
| 'automaticAdapterUpgradeSuccessful'
1072+
| 'blockedVersions'
1073+
| 'databaseErrors'
1074+
| 'securityIssues'
1075+
| 'packageUpdates'
1076+
| 'systemRebootRequired'
1077+
| 'diskSpaceIssues'
1078+
| string;
1079+
/** The human-readable category name */
1080+
name: Translated;
1081+
/** The human-readable category description */
1082+
description: Translated;
1083+
/** Allows to define the severity of the notification with `info` being the lowest `notify` representing middle priority, `alert` representing high priority and often containing critical information */
1084+
severity: 'info' | 'notify' | 'alert';
1085+
/** If a regex is specified, the js-controller will check error messages on adapter crashes against this regex and will generate a notification of this category */
1086+
regex: string[];
1087+
/** Deletes older messages if more than the specified amount is present for this category */
1088+
limit: number;
1089+
};
10531090

10541091
interface Notification {
1092+
/** Each adapter can define its own "scopes" for own notifications with its own categories which then will be available in the system. Adapters should only register one scope which matches the name of the adapter. */
10551093
scope: string;
1094+
/** The human-readable name of this scope */
10561095
name: Translated;
1096+
/** The human-readable description of this scope */
10571097
description: Translated;
1098+
/** All notification categories of this scope */
10581099
categories: NotificationCategory[];
10591100
}
10601101

0 commit comments

Comments
 (0)