Termine gebündelt ausgeben #1

Merged
fenris merged 4 commits from task-340 into main 2025-06-30 13:06:51 +02:00
7 changed files with 380 additions and 179 deletions
Showing only changes of commit 1ade4dec2a - Show all commits

View file

@ -24,6 +24,24 @@ along with »munin«. If not, see <http://www.gnu.org/licenses/>.
namespace _munin.conf
{
/**
*/
type type_reminder_raw = {
frequency : (
"hourly"
|
"daily"
|
"weekly"
|
"monthly"
);
offset : int;
from : int;
to : int;
};
/**
*/
type type_conf_v1 = {
@ -236,7 +254,71 @@ namespace _munin.conf
/**
*/
export type type_conf = type_conf_v4;
type type_conf_v5 = {
sources : Array<
(
{
kind : "ical_feed";
data : {
url : string;
filtration : {
category_whitelist : (null | Array<string>);
category_blacklist : (null | Array<string>);
title_whitelist : (null | Array<string>);
title_blacklist : (null | Array<string>);
}
}
}
)
>;
targets : Array<
(
{
kind : "email";
data : {
smtp_host : string;
smtp_port : int;
smtp_username : string;
smtp_password : string;
sender : string;
receivers : Array<string>;
hide_tags : boolean;
/**
* in hours
*/
reminders : Array<type_reminder_raw>;
}
}
|
{
kind : "telegram_bot";
data : {
bot_token : string;
chat_id : int;
hide_tags : boolean;
/**
* in hours
*/
reminders : Array<type_reminder_raw>;
}
}
)
>;
settings : {
interval : float;
};
labels : {
head : string;
title : string;
time : string;
location : string;
};
};
/**
*/
export type type_conf = type_conf_v5;
/**
@ -396,6 +478,65 @@ namespace _munin.conf
}
/**
*/
function convert_from_v4(
conf_v4 : type_conf_v4
) : type_conf_v5
{
const map_reminder = hours => ({
"frequency": "hourly",
"offset": 0,
"from": hours,
"to": (hours + 1),
} as type_reminder_raw);
return {
"sources": conf_v4.sources,
"targets": conf_v4.targets.map(
target => {
switch (target.kind) {
case "email": {
return {
"kind": "email",
"data": {
"smtp_host": target.data.smtp_host,
"smtp_port": target.data.smtp_port,
"smtp_username": target.data.smtp_username,
"smtp_password": target.data.smtp_password,
"sender": target.data.sender,
"receivers": target.data.receivers,
"hide_tags": target.data.hide_tags,
"reminders": target.data.reminders.map(map_reminder),
},
};
break;
}
case "telegram_bot": {
return {
"kind": "telegram_bot",
"data": {
"bot_token": target.data.bot_token,
"chat_id": target.data.chat_id,
"hide_tags": target.data.hide_tags,
"reminders": target.data.reminders.map(map_reminder),
},
};
break;
}
default: {
// return target;
throw (new Error("unhandled target kind: " + String(target)));
break;
}
}
}
),
"settings": conf_v4.settings,
"labels": conf_v4.labels,
};
}
/**
*/
function schema_source_kalender_digital(
@ -546,6 +687,136 @@ namespace _munin.conf
}
/**
*/
function schema_sources(
version : string
) : lib_plankton.conf.type_schema
{
switch (version) {
case "1": {
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_source_kalender_digital(version),
],
}
};
break;
}
default:
case "2": {
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_source_ical_feed(version),
],
}
};
break;
}
}
}
/**
*/
function schema_reminder(
version : string
) : lib_plankton.conf.type_schema
{
switch (version)
{
case "1":
case "2":
case "3":
case "4":
{
return {
"type": "integer",
};
}
case "5":
default:
{
return {
"nullable": false,
"type": "object",
"properties": {
"frequency": {
"nullable": false,
"type": "string",
"enum": [
"hourly",
"daily",
"weekly",
"monthly",
]
},
"offset": {
"nullable": false,
"type": "integer",
"default": 0
},
"from": {
"nullable": false,
"type": "integer"
},
"to": {
"nullable": false,
"type": "integer"
},
},
"additionalProperties": false,
"required": [
"frequency",
"from",
"to",
]
};
break;
}
}
}
/**
*/
function default_reminder(
version : string
) : any
{
switch (version)
{
case "1":
case "2":
case "3":
case "4":
{
return [24];
}
case "5":
default:
{
return [
{
"frequency": "hourly",
"from": 24,
"to": 25
}
];
break;
}
}
}
/**
*/
function schema_target_email(
@ -602,11 +873,8 @@ namespace _munin.conf
"reminders": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "integer"
},
"default": [24.0],
"items": schema_reminder(version),
"default": default_reminder(version),
},
},
"additionalProperties": false,
@ -628,44 +896,6 @@ namespace _munin.conf
}
/**
*/
function schema_sources(
version : string
) : lib_plankton.conf.type_schema
{
switch (version) {
case "1": {
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_source_kalender_digital(version),
],
}
};
break;
}
default:
case "2": {
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_source_ical_feed(version),
],
}
};
break;
}
}
}
/**
*/
function schema_target_telegram_bot(
@ -701,11 +931,8 @@ namespace _munin.conf
"reminders": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "integer"
},
"default": [24.0],
"items": schema_reminder(version),
"default": default_reminder(version),
},
},
"additionalProperties": false,
@ -737,13 +964,18 @@ namespace _munin.conf
"nullable": false,
"anyOf": (() => {
switch (version) {
default: {
case "1":
case "2":
{
return [
schema_target_telegram_bot(version),
];
break;
}
case "3": {
case "4":
case "5":
default:
{
return [
schema_target_email(version),
schema_target_telegram_bot(version),
@ -824,7 +1056,7 @@ namespace _munin.conf
/**
*/
export function schema(
version : string = "4"
version : string = "5"
) : lib_plankton.conf.type_schema
{
switch (version) {
@ -852,7 +1084,9 @@ namespace _munin.conf
}
case "2":
case "3":
case "4": {
case "4":
case "5":
{
return {
"nullable": false,
"type": "object",
@ -887,7 +1121,8 @@ namespace _munin.conf
"1": {"target": "2", "function": convert_from_v1},
"2": {"target": "3", "function": convert_from_v2},
"3": {"target": "4", "function": convert_from_v3},
"4": null,
"4": {"target": "5", "function": convert_from_v4},
"5": null,
}
);
@ -925,7 +1160,7 @@ namespace _munin.conf
}
}
*/
return (conf_raw.content as type_conf_v4);
return (conf_raw.content as type_conf_v5);
}
}

View file

@ -21,45 +21,6 @@ along with »munin«. If not, see <http://www.gnu.org/licenses/>.
namespace _munin.logic
{
/**
* @todo Tests schreiben
*/
function shall_remind(
event : _munin.type_event,
reminder : type_reminder,
{
"pit": pit = lib_plankton.pit.now(),
"interval": interval = 1,
} : {
pit ?: lib_plankton.pit.type_pit;
interval ?: int;
} = {
}
) : boolean
{
const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
pit,
0
);
const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
pit,
interval
);
const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime(
event.begin
);
const reminder_time : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
event_begin,
(-reminder)
);
return lib_plankton.pit.is_between(
reminder_time,
window_from,
window_to
);
}
/**
*/
function frequency_anchor(
@ -90,7 +51,7 @@ namespace _munin.logic
/**
*/
export function reminder_due(
reminder : type_reminder_new,
reminder : type_reminder,
{
"pit": pit = lib_plankton.pit.now(),
"interval": interval = 1,
@ -105,35 +66,46 @@ namespace _munin.logic
reminder.frequency,
{"pit": pit}
);
// 0 ≤ ((p - a(p)) - o) < i
const x : float = (
(
(
lib_plankton.pit.to_unix_timestamp(pit)
-
lib_plankton.pit.to_unix_timestamp(anchor)
)
/
(60 * 60)
)
-
reminder.offset
const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
anchor,
(reminder.offset + 0)
);
return ((0 <= x) && (x < interval));
const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
anchor,
(reminder.offset + interval)
);
const result : boolean = lib_plankton.pit.is_between(
pit,
window_from,
window_to
);
lib_plankton.log._info(
"munin.logic.reminder_due",
{
"details": {
"reminder": reminder,
"datetime": lib_plankton.pit.to_datetime(pit),
"interval": interval,
"anchor": lib_plankton.pit.to_datetime(anchor),
"window_from": lib_plankton.pit.to_datetime(window_from),
"window_to": lib_plankton.pit.to_datetime(window_to),
"result": result,
}
}
);
return result;
}
/**
*/
export function reminder_event_in_window(
reminder : type_reminder_new,
reminder : type_reminder,
event : _munin.type_event,
{
"pit": pit = lib_plankton.pit.now(),
"interval": interval = 1,
} : {
pit ?: lib_plankton.pit.type_pit;
interval ?: int;
} = {
}
) : boolean
@ -153,41 +125,26 @@ namespace _munin.logic
const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime(
event.begin
);
return lib_plankton.pit.is_between(
const result : boolean = lib_plankton.pit.is_between(
event_begin,
window_from,
window_to
);
}
/**
*/
function shall_remind_new(
reminder : type_reminder_new,
event : _munin.type_event,
lib_plankton.log._info(
"munin.logic.reminder_event_in_window",
{
"pit": pit = lib_plankton.pit.now(),
"interval": interval = 1,
} : {
pit ?: lib_plankton.pit.type_pit;
interval ?: int;
} = {
"details": {
"reminder": reminder,
"event": event,
"datetime": lib_plankton.pit.to_datetime(pit),
"anchor": lib_plankton.pit.to_datetime(anchor),
"window_from": lib_plankton.pit.to_datetime(window_from),
"window_to": lib_plankton.pit.to_datetime(window_to),
"result": result,
}
}
) : boolean
{
return (
reminder_due(
reminder,
{"pit": pit, "interval": interval}
)
&&
reminder_event_in_window(
reminder,
event,
{"pit": pit, "interval": interval}
)
);
return result;
}
@ -211,17 +168,30 @@ namespace _munin.logic
.reduce((x, y) => x.concat(y), [])
);
for (const target of targets) {
for (const reminder_hours of target.reminders) {
for (const event of events) {
const remind : boolean = shall_remind(
event,
reminder_hours,
for (const reminder of target.reminders) {
const due : boolean = reminder_due(
reminder,
{
"pit": now,
"interval": conf.settings.interval,
}
);
if (! remind) {
if (! due)
{
// do nothing
}
else
{
const events_matching : Array<_munin.type_event> = events.filter(
event => reminder_event_in_window(
reminder,
event,
{
"pit": now,
}
)
);
if (events_matching.length <= 0) {
// do nothing
}
else {
@ -229,7 +199,7 @@ namespace _munin.logic
"munin.remind.do",
{
"details": {
"event": event,
"events": events,
"target": target.show(),
}
}
@ -238,6 +208,11 @@ namespace _munin.logic
// do nothing
}
else {
/**
* @todo bundle?
*/
for (const event of events_matching)
{
try {
await target.send(conf.labels, event);
}
@ -257,6 +232,7 @@ namespace _munin.logic
}
}
}
}
/**

View file

@ -158,8 +158,9 @@ namespace _munin
"data": {
"target": "stdout",
"format": {
"kind": "human_readable",
"kind": "jsonl",
"data": {
"structured": true
}
}
}

View file

@ -31,10 +31,7 @@ namespace _munin.targets.email
sender : string;
receivers : Array<string>;
hide_tags : boolean;
/**
* in hours
*/
reminders : Array<int>;
reminders : Array<_munin.type_reminder>;
};

View file

@ -27,7 +27,7 @@ namespace _munin.targets.telegram_bot
bot_token : string;
chat_id : int;
hide_tags : boolean;
reminders : Array<int>;
reminders : Array<_munin.type_reminder>;
};

View file

@ -359,7 +359,6 @@ namespace _munin.test
},
{
"pit": datetime_to_pit(testcase.input.datetime),
"interval": testcase.input.interval,
}
);

View file

@ -67,15 +67,8 @@ namespace _munin
/**
* @todo rename
* @todo extend
*/
export type type_reminder = int;
/**
* @todo rename
*/
export type type_reminder_new = {
export type type_reminder = {
frequency : enum_frequency;
offset : int;
from : int;