iobroker.kanban 0.3.0
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 +224 -0
- package/admin/i18n/de.json +100 -0
- package/admin/i18n/en.json +100 -0
- package/admin/i18n/es.json +100 -0
- package/admin/i18n/fr.json +100 -0
- package/admin/i18n/it.json +100 -0
- package/admin/i18n/nl.json +100 -0
- package/admin/i18n/pl.json +100 -0
- package/admin/i18n/pt.json +100 -0
- package/admin/i18n/ru.json +100 -0
- package/admin/i18n/uk.json +100 -0
- package/admin/i18n/zh-cn.json +100 -0
- package/admin/jsonConfig.json +668 -0
- package/admin/kanban.png +0 -0
- package/io-package.json +299 -0
- package/lib/cron.js +203 -0
- package/lib/dueat.js +77 -0
- package/lib/events.js +24 -0
- package/lib/holidays.js +167 -0
- package/lib/i18n-server.js +301 -0
- package/lib/notify.js +627 -0
- package/lib/scheduler.js +198 -0
- package/lib/server.js +799 -0
- package/lib/store.js +1500 -0
- package/main.js +382 -0
- package/package.json +58 -0
- package/www/css/app.css +1022 -0
- package/www/i18n/de.json +238 -0
- package/www/i18n/en.json +238 -0
- package/www/i18n/es.json +238 -0
- package/www/i18n/fr.json +238 -0
- package/www/i18n/it.json +238 -0
- package/www/i18n/nl.json +238 -0
- package/www/i18n/pl.json +238 -0
- package/www/i18n/pt.json +238 -0
- package/www/i18n/ru.json +238 -0
- package/www/i18n/uk.json +238 -0
- package/www/i18n/zh-cn.json +238 -0
- package/www/index.html +171 -0
- package/www/js/api.js +98 -0
- package/www/js/app.js +411 -0
- package/www/js/board.js +983 -0
- package/www/js/colorpicker.js +165 -0
- package/www/js/dialogs.js +1419 -0
- package/www/js/i18n.js +43 -0
- package/www/vendor/marked.min.js +69 -0
- package/www/vendor/purify.min.js +3 -0
- package/www/vendor/sortable.min.js +2 -0
package/lib/scheduler.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { todayStr } = require('./store');
|
|
4
|
+
|
|
5
|
+
/** Aktuelle Ortszeit als HH:MM. */
|
|
6
|
+
function nowHHMM() {
|
|
7
|
+
const now = new Date();
|
|
8
|
+
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fälligkeits-/Erinnerungs-Check.
|
|
13
|
+
* 60-s-Tick: aktualisiert Spiegel-States; wenn die Uhrzeit reminderTime (HH:MM)
|
|
14
|
+
* erreicht ist, werden für fällige Karten cardDue-Events emittiert.
|
|
15
|
+
* Zusätzlich ein Lauf 30 s nach Adapterstart (falls reminderTime verpasst wurde,
|
|
16
|
+
* werden nur Karten erinnert, deren lastReminderAt nicht heute ist).
|
|
17
|
+
*/
|
|
18
|
+
class Scheduler {
|
|
19
|
+
constructor(adapter, store, bus) {
|
|
20
|
+
this.adapter = adapter;
|
|
21
|
+
this.store = store;
|
|
22
|
+
this.bus = bus;
|
|
23
|
+
this._interval = null;
|
|
24
|
+
this._startupTimer = null;
|
|
25
|
+
this._lastMinute = '';
|
|
26
|
+
this._lastCleanupDay = '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
start() {
|
|
30
|
+
this._interval = this.adapter.setInterval(() => this._tick(), 60 * 1000);
|
|
31
|
+
this._startupTimer = this.adapter.setTimeout(() => {
|
|
32
|
+
this._runDueCheck(true).catch(e => this.adapter.log.error(`Due check: ${e.message}`));
|
|
33
|
+
this._runCleanup().catch(e => this.adapter.log.error(`Cleanup: ${e.message}`));
|
|
34
|
+
// Uhrzeit-Fälligkeiten des laufenden Tages nachholen, die während eines
|
|
35
|
+
// Neustarts oder einer Ausfallzeit verpasst wurden
|
|
36
|
+
this._runExactDueCheck(nowHHMM()).catch(e => this.adapter.log.error(`Exact due check: ${e.message}`));
|
|
37
|
+
}, 30 * 1000);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
stop() {
|
|
41
|
+
if (this._interval) {
|
|
42
|
+
this.adapter.clearInterval(this._interval);
|
|
43
|
+
}
|
|
44
|
+
if (this._startupTimer) {
|
|
45
|
+
this.adapter.clearTimeout(this._startupTimer);
|
|
46
|
+
}
|
|
47
|
+
this._interval = null;
|
|
48
|
+
this._startupTimer = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_tick() {
|
|
52
|
+
this.store.updateMirrors().catch(e => this.adapter.log.error(`Mirror states: ${e.message}`));
|
|
53
|
+
const hhmm = nowHHMM();
|
|
54
|
+
if (hhmm === this._lastMinute) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this._lastMinute = hhmm;
|
|
58
|
+
if (hhmm === (this.adapter.config.reminderTime || '08:00')) {
|
|
59
|
+
this._runDueCheck(false).catch(e => this.adapter.log.error(`Due check: ${e.message}`));
|
|
60
|
+
}
|
|
61
|
+
// Kartengenaue Fälligkeit (optional): jede Minute prüfen
|
|
62
|
+
this._runExactDueCheck(hhmm).catch(e => this.adapter.log.error(`Exact due check: ${e.message}`));
|
|
63
|
+
// Aufräumen einmal täglich (beim ersten Tick eines neuen Kalendertags)
|
|
64
|
+
const day = todayStr();
|
|
65
|
+
if (day !== this._lastCleanupDay) {
|
|
66
|
+
this._runCleanup().catch(e => this.adapter.log.error(`Cleanup: ${e.message}`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Automatischen Aufräumlauf ausführen und die gebündelte E-Mail-Zusammenfassung verschicken. */
|
|
71
|
+
async _runCleanup() {
|
|
72
|
+
this._lastCleanupDay = todayStr();
|
|
73
|
+
const results = this.store.runCleanup(Date.now());
|
|
74
|
+
const moved = results.reduce((n, r) => n + r.moved.length, 0);
|
|
75
|
+
const purged = results.reduce((n, r) => n + r.purged.length, 0);
|
|
76
|
+
if (moved || purged) {
|
|
77
|
+
this.adapter.log.info(`Cleanup: ${moved} card(s) moved to the trash, ${purged} permanently removed`);
|
|
78
|
+
if (this.adapter.notifier && this.adapter.notifier.sendCleanupSummary) {
|
|
79
|
+
await this.adapter.notifier.sendCleanupSummary(results);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Kartengenaue Fälligkeit (Instanz-Option „Fällig-Ereignis zur Uhrzeit").
|
|
86
|
+
* Für Karten mit gesetzter Uhrzeit wird ein zusätzliches cardDue-Ereignis mit
|
|
87
|
+
* `detail.exact = true` ausgelöst, sobald die Uhrzeit erreicht ist – unabhängig
|
|
88
|
+
* von der täglichen Erinnerungs-Uhrzeit. `lastExactAt` (Datum) verhindert
|
|
89
|
+
* Wiederholungen und holt eine verpasste Minute (z. B. Adapterstart) am
|
|
90
|
+
* gleichen Tag nach.
|
|
91
|
+
*
|
|
92
|
+
* @param hhmm aktuelle Uhrzeit als HH:MM
|
|
93
|
+
*/
|
|
94
|
+
async _runExactDueCheck(hhmm) {
|
|
95
|
+
if (!this.adapter.config.dueExact) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const today = todayStr();
|
|
99
|
+
let count = 0;
|
|
100
|
+
for (const board of this.store.boards.values()) {
|
|
101
|
+
let touched = false;
|
|
102
|
+
for (const card of board.cards) {
|
|
103
|
+
if (!card.due || !card.dueTime) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (card.due !== today || card.dueTime > hhmm) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (card.lastExactAt === today) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (this.store._isDoneColumn(board, card.columnId)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (this.store._isTrashColumn(board, card.columnId)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
card.lastExactAt = today;
|
|
119
|
+
touched = true;
|
|
120
|
+
count++;
|
|
121
|
+
this.bus.emitEvent('cardDue', {
|
|
122
|
+
board: { id: board.id, title: board.title },
|
|
123
|
+
card,
|
|
124
|
+
detail: { due: card.due, dueTime: card.dueTime, exact: true, overdue: false },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (touched) {
|
|
128
|
+
board.rev++;
|
|
129
|
+
this.store._schedulePersist(board.id);
|
|
130
|
+
if (this.store.onChange) {
|
|
131
|
+
this.store.onChange(board.id, board.rev);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (count) {
|
|
136
|
+
this.adapter.log.info(`Exact due check: ${count} event(s) fired`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param startup true = Lauf nach Adapterstart: nur nachholen, wenn reminderTime
|
|
142
|
+
* heute schon vorbei ist (sonst würde die Erinnerung zu früh kommen)
|
|
143
|
+
*/
|
|
144
|
+
async _runDueCheck(startup) {
|
|
145
|
+
const cfg = this.adapter.config;
|
|
146
|
+
if (startup) {
|
|
147
|
+
const now = new Date();
|
|
148
|
+
const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
149
|
+
if (hhmm < (cfg.reminderTime || '08:00')) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const daysBefore = Number(cfg.reminderDaysBefore) || 0;
|
|
154
|
+
const threshold = new Date();
|
|
155
|
+
threshold.setDate(threshold.getDate() + daysBefore);
|
|
156
|
+
const thresholdStr = `${threshold.getFullYear()}-${String(threshold.getMonth() + 1).padStart(2, '0')}-${String(threshold.getDate()).padStart(2, '0')}`;
|
|
157
|
+
const today = todayStr();
|
|
158
|
+
|
|
159
|
+
let count = 0;
|
|
160
|
+
for (const board of this.store.boards.values()) {
|
|
161
|
+
let touched = false;
|
|
162
|
+
for (const card of board.cards) {
|
|
163
|
+
if (!card.due || card.due > thresholdStr) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (this.store._isDoneColumn(board, card.columnId)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (this.store._isTrashColumn(board, card.columnId)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (card.lastReminderAt === today) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
card.lastReminderAt = today;
|
|
176
|
+
touched = true;
|
|
177
|
+
count++;
|
|
178
|
+
this.bus.emitEvent('cardDue', {
|
|
179
|
+
board: { id: board.id, title: board.title },
|
|
180
|
+
card,
|
|
181
|
+
detail: { due: card.due, overdue: card.due < today },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (touched) {
|
|
185
|
+
board.rev++;
|
|
186
|
+
this.store._schedulePersist(board.id);
|
|
187
|
+
if (this.store.onChange) {
|
|
188
|
+
this.store.onChange(board.id, board.rev);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (count) {
|
|
193
|
+
this.adapter.log.info(`Due check: ${count} reminder(s) fired`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = { Scheduler };
|