Skip to content

Commit

Permalink
[types]: Added types for Notifications on Adapter/Instances (#2874)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
GermanBluefox and foxriver76 authored Sep 2, 2024
1 parent 3718997 commit dbd1caa
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 53 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ The definition also contains localized names and descriptions that can be used t
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.
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.

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.
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.

```json
{
Expand Down Expand Up @@ -534,7 +534,7 @@ The message needs to take the following parameters in the message object:
* categoryFilter - category of notifications
* instanceFilter - instance of notifications

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

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

The js-controller and each adapter has defined its own log level. By default, `info` is used. The following log levels can be used:
The js-controller and each adapter can define their own log level. By default, `info` is used. The following log levels can be used:
* silly (most logging)
* debug
* info
Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/lib/setup/setupRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ export class Repo {
systemRepos?: ioBroker.RepositoryObject
): Promise<null | ioBroker.RepositoryJson> {
if (!repoName) {
const sysConfig = systemConfig || (await this.objects.getObjectAsync('system.config'));
const sysConfig = systemConfig || (await this.objects.getObject('system.config'));
repoName = sysConfig!.common.activeRepo;
}

const oldRepos = systemRepos || (await this.objects.getObjectAsync('system.repositories'));
const oldRepos = systemRepos || (await this.objects.getObject('system.repositories'));
if (!oldRepos?.native.repositories?.[repoName]) {
console.log(`Error: repository "${repoName}" not found in the "system.repositories`);
return null;
Expand All @@ -101,7 +101,7 @@ export class Repo {
(urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://'))
) {
try {
hash = await axios({ url: hashUrl, timeout: 10000 });
hash = await axios({ url: hashUrl, timeout: 10_000 });
} catch (e) {
console.error(`Cannot download repository hash file from "${hashUrl}": ${e.message}`);
}
Expand Down Expand Up @@ -161,7 +161,7 @@ export class Repo {
if (changed) {
oldRepos.from = `system.host.${tools.getHostName()}.cli`;
oldRepos.ts = Date.now();
await this.objects.setObjectAsync('system.repositories', oldRepos);
await this.objects.setObject('system.repositories', oldRepos);
}

return oldRepos.native.repositories[repoName].json;
Expand Down Expand Up @@ -319,8 +319,8 @@ export class Repo {
const listStr = list.join(', ');
for (const row of objs.rows) {
if (row?.value?.type === 'instance') {
await this.states.setStateAsync(`${row.id}.info.updatesNumber`, { val: list.length, ack: true });
await this.states.setStateAsync(`${row.id}.info.updatesList`, { val: listStr, ack: true });
await this.states.setState(`${row.id}.info.updatesNumber`, { val: list.length, ack: true });
await this.states.setState(`${row.id}.info.updatesList`, { val: listStr, ack: true });
}
}
}
Expand Down Expand Up @@ -412,7 +412,7 @@ export class Repo {
delete repoObj.native.repositories[repoName];
repoObj.from = `system.host.${tools.getHostName()}.cli`;
repoObj.ts = Date.now();
await this.objects.setObjectAsync('system.repositories', repoObj);
await this.objects.setObject('system.repositories', repoObj);
}
}
}
Expand Down
78 changes: 42 additions & 36 deletions packages/controller/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ interface RepoRequester {
callback: ioBroker.MessageCallbackInfo;
}

/** Host information including host id and running version */
type HostInformation = ioBroker.HostCommon & { host: string; runningVersion: string };

const VIS_ADAPTERS = ['vis', 'vis-2'] as const;
const ioPackage = fs.readJSONSync(path.join(tools.getControllerDir(), 'io-package.json'));
const version = ioPackage.common.version;
Expand Down Expand Up @@ -1280,7 +1283,6 @@ function cleanAutoSubscribes(instanceID: ioBroker.ObjectIDs.Instance, callback:
// remove this instance from autoSubscribe
if (row.value?.common.subscribable) {
count++;
// @ts-expect-error https://github.com/ioBroker/ioBroker.js-controller/issues/2089
cleanAutoSubscribe(instance, row.id, () => !--count && callback && callback());
}
}
Expand Down Expand Up @@ -1832,7 +1834,7 @@ function initMessageQueue(): void {
}

/**
* Send a message to other adapter instance
* Send a message to another adapter instance
*
* @param objName - adapter name (hm-rpc) or id like system.host.rpi/system.adapter,hm-rpc
* @param command
Expand Down Expand Up @@ -1891,10 +1893,11 @@ async function sendTo(
}

/**
* Get the version information from given host
*
* @param hostId
* @param hostId host to get the version information from
*/
async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Record<string, any> | null | undefined> {
async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<HostInformation | null> {
const state = await states!.getState(`${hostId}.alive`);
if (state?.val) {
return new Promise(resolve => {
Expand All @@ -1908,6 +1911,7 @@ async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Reco
if (timeout) {
clearTimeout(timeout);
timeout = null;
// @ts-expect-error sendTo needs to be fixed, because in some cases there is no error and return value is in first arg
resolve(ioPack);
}
});
Expand All @@ -1918,6 +1922,9 @@ async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise<Reco
}
}

/**
* Upload all adapters which are currently in `uploadTasks` queue
*/
async function startAdapterUpload(): Promise<void> {
if (!uploadTasks.length) {
return;
Expand Down Expand Up @@ -2211,40 +2218,34 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
case 'getInstalled':
if (msg.callback && msg.from) {
// Get a list of all hosts
objects!.getObjectView(
'system',
'host',
{
startkey: 'system.host.',
endkey: 'system.host.\u9999'
},
async (err, doc) => {
const result: Record<string, any> = tools.getInstalledInfo(version);
result.hosts = {};
if (doc?.rows.length) {
// Read installed versions of all hosts
for (const row of doc.rows) {
// If desired a local version, do not ask it, just answer
if (row.id === hostObjectPrefix) {
const ioPackCommon = deepClone(ioPackage.common);

ioPackCommon.host = hostname;
ioPackCommon.runningVersion = version;
result.hosts[hostname] = ioPackCommon;
} else {
// @ts-expect-error https://github.com/ioBroker/ioBroker.js-controller/issues/2089
const ioPack = await getVersionFromHost(row.id);
if (ioPack) {
result.hosts[ioPack.host] = ioPack;
result.hosts[ioPack.host].controller = true;
}
}
const doc = await objects!.getObjectViewAsync('system', 'host', {
startkey: 'system.host.',
endkey: 'system.host.\u9999'
});

const installedInfo = tools.getInstalledInfo();
const hosts: Record<string, HostInformation> = {};

if (doc?.rows.length) {
// Read installed versions of all hosts
for (const row of doc.rows) {
// If desired a local version, do not ask it, just answer
if (row.id === hostObjectPrefix) {
const ioPackCommon = deepClone(ioPackage.common);

ioPackCommon.host = hostname;
ioPackCommon.runningVersion = version;
hosts[hostname] = ioPackCommon;
} else {
const ioPack = await getVersionFromHost(row.id);
if (ioPack) {
hosts[ioPack.host] = ioPack;
}
}

sendTo(msg.from, msg.command, result, msg.callback);
}
);
}

sendTo(msg.from, msg.command, { ...installedInfo, hosts }, msg.callback);
} else {
logger.error(`${hostLogPrefix} Invalid request ${msg.command}. "callback" or "from" is null`);
}
Expand All @@ -2270,7 +2271,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi

case 'getVersion':
if (msg.callback && msg.from) {
const ioPackCommon = deepClone(ioPackage.common);
const ioPackCommon: ioBroker.HostCommon & { host: string; runningVersion: string } = deepClone(
ioPackage.common
);
ioPackCommon.host = hostname;
ioPackCommon.runningVersion = version;
sendTo(msg.from, msg.command, ioPackCommon, msg.callback);
Expand Down Expand Up @@ -3033,6 +3036,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
}
}

/**
* Collect all instances on this host and call `initInstances`
*/
async function getInstances(): Promise<void> {
if (!objects) {
throw new Error('Objects database not connected');
Expand Down
2 changes: 1 addition & 1 deletion packages/types-dev/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ declare global {

interface GetObjectViewItem<T extends AnyObject> {
/** The ID of this object */
id: string;
id: T['_id'];
/** A copy of the object from the DB */
value: T;
}
Expand Down
53 changes: 47 additions & 6 deletions packages/types-dev/objects.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@ declare global {
description?: ioBroker.StringOrTranslated;
};

/** Format for local and global dependencies */
type Depdendencies = { [adapterName: string]: string }[] | string[];

interface AdapterCommon extends ObjectCommon {
/** Custom attributes to be shown in admin in the object browser */
adminColumns?: string | (string | CustomAdminColumn)[];
Expand Down Expand Up @@ -632,9 +635,9 @@ declare global {
/** How the adapter will mainly receive its data. Set this together with @see connectionType */
dataSource?: 'poll' | 'push' | 'assumption';
/** A record of ioBroker adapters (including "js-controller") and version ranges which are required for this adapter on the same host. */
dependencies?: Array<Record<string, string>>;
dependencies?: Depdendencies;
/** A record of ioBroker adapters (including "js-controller") and version ranges which are required for this adapter in the whole system. */
globalDependencies?: Array<Record<string, string>>;
globalDependencies?: Depdendencies;
/** Which files outside the README.md have documentation for the adapter */
docs?: Partial<Record<Languages, string | string[]>>;
/** Whether new instances should be enabled by default. *Should* be `false`! */
Expand Down Expand Up @@ -822,6 +825,8 @@ declare global {
defaultLogLevel?: LogLevel;
/** Used date format for formatting */
dateFormat: string;
/** This name will be shown in admin's header. Just to identify the whole installation */
siteName?: string;
/** Default acl for new objects */
defaultNewAcl: {
object: number;
Expand Down Expand Up @@ -865,9 +870,9 @@ declare global {
}

/**
* 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.
* 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.
* 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.
* 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".
* 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".
* If the password is correct, the object attributes will be updated. If the password is wrong, the object will not be changed.
* 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.
*/
Expand Down Expand Up @@ -1012,6 +1017,8 @@ declare global {
json: RepositoryJson | null;
hash?: string;
time?: string;
/** If this repository stable */
stable?: boolean;
}

interface RepositoryObject extends BaseObject {
Expand Down Expand Up @@ -1048,13 +1055,47 @@ declare global {
common?: Partial<InstanceCommon>;
}

/** TODO: To be defined */
type NotificationCategory = any;
// it is defined in notificationHandler.ts
type NotificationCategory = {
/** The unique category identifier */
category:
| 'memIssues'
| 'fsIoErrors'
| 'noDiskSpace'
| 'accessErrors'
| 'nonExistingFileErrors'
| 'remoteHostErrors'
| 'restartLoop'
| 'fileToJsonl'
| 'automaticAdapterUpgradeFailed'
| 'automaticAdapterUpgradeSuccessful'
| 'blockedVersions'
| 'databaseErrors'
| 'securityIssues'
| 'packageUpdates'
| 'systemRebootRequired'
| 'diskSpaceIssues'
| string;
/** The human-readable category name */
name: Translated;
/** The human-readable category description */
description: Translated;
/** 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 */
severity: 'info' | 'notify' | 'alert';
/** 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 */
regex: string[];
/** Deletes older messages if more than the specified amount is present for this category */
limit: number;
};

interface Notification {
/** 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. */
scope: string;
/** The human-readable name of this scope */
name: Translated;
/** The human-readable description of this scope */
description: Translated;
/** All notification categories of this scope */
categories: NotificationCategory[];
}

Expand Down

0 comments on commit dbd1caa

Please sign in to comment.