signalk-maintenance-tracker 1.0.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/CHANGELOG.md +46 -0
- package/LICENSE +201 -0
- package/README.md +75 -0
- package/dist/api/router.js +193 -0
- package/dist/api/router.js.map +1 -0
- package/dist/auth.js +22 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.js +50 -0
- package/dist/config.js.map +1 -0
- package/dist/db/database.js +47 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/logs.repo.js +91 -0
- package/dist/db/logs.repo.js.map +1 -0
- package/dist/db/migrations.js +57 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/tags.repo.js +78 -0
- package/dist/db/tags.repo.js.map +1 -0
- package/dist/db/tasks.repo.js +78 -0
- package/dist/db/tasks.repo.js.map +1 -0
- package/dist/domain/slug.js +27 -0
- package/dist/domain/slug.js.map +1 -0
- package/dist/domain/status.js +110 -0
- package/dist/domain/status.js.map +1 -0
- package/dist/index.js +130 -0
- package/dist/index.js.map +1 -0
- package/dist/service.js +370 -0
- package/dist/service.js.map +1 -0
- package/dist/signalk/notifications.js +115 -0
- package/dist/signalk/notifications.js.map +1 -0
- package/dist/signalk/runtime.js +103 -0
- package/dist/signalk/runtime.js.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/docs/screenshots/completed.png +0 -0
- package/docs/screenshots/dark-mode.png +0 -0
- package/docs/screenshots/edit-task.png +0 -0
- package/docs/screenshots/homepage.png +0 -0
- package/docs/screenshots/main-log.png +0 -0
- package/docs/screenshots/task-detail.png +0 -0
- package/package.json +82 -0
- package/public/app/api/client.js +75 -0
- package/public/app/api/hooks.js +159 -0
- package/public/app/api/resource.js +156 -0
- package/public/app/api/signalkPaths.js +66 -0
- package/public/app/app.js +71 -0
- package/public/app/auth/auth.js +158 -0
- package/public/app/components/AuthControl.js +22 -0
- package/public/app/components/ConfirmModal.js +55 -0
- package/public/app/components/LogEntryModal.js +150 -0
- package/public/app/components/LoginModal.js +86 -0
- package/public/app/components/MarkdownView.js +17 -0
- package/public/app/components/Modal.js +89 -0
- package/public/app/components/Pagination.js +35 -0
- package/public/app/components/PathPicker.js +66 -0
- package/public/app/components/ProgressBar.js +26 -0
- package/public/app/components/StatusBadge.js +9 -0
- package/public/app/components/Table.js +99 -0
- package/public/app/components/TagInput.js +93 -0
- package/public/app/components/TaskFormModal.js +310 -0
- package/public/app/components/ThemeToggle.js +17 -0
- package/public/app/components/Toaster.js +24 -0
- package/public/app/lib/format.js +90 -0
- package/public/app/lib/html.js +6 -0
- package/public/app/lib/router.js +105 -0
- package/public/app/lib/sanitize.js +64 -0
- package/public/app/lib/slug.js +16 -0
- package/public/app/lib/theme.js +52 -0
- package/public/app/lib/toasts.js +33 -0
- package/public/app/lib/useListParams.js +28 -0
- package/public/app/main.js +20 -0
- package/public/app/pages/MasterLogPage.js +279 -0
- package/public/app/pages/TaskDetailPage.js +294 -0
- package/public/app/pages/TaskListPage.js +279 -0
- package/public/app/types.js +90 -0
- package/public/icons/apple-touch-icon.png +0 -0
- package/public/icons/favicon-16.png +0 -0
- package/public/icons/favicon-32.png +0 -0
- package/public/icons/favicon.ico +0 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/index.html +82 -0
- package/public/manifest.webmanifest +37 -0
- package/public/styles/main.css +787 -0
- package/public/styles/tokens.css +55 -0
- package/public/vendor/VERSIONS.json +9 -0
- package/public/vendor/bootstrap-icons/bootstrap-icons.css +2106 -0
- package/public/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- package/public/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- package/public/vendor/dayjs/constant.js +25 -0
- package/public/vendor/dayjs/index.d.ts +1 -0
- package/public/vendor/dayjs/index.js +541 -0
- package/public/vendor/dayjs/locale/en.js +12 -0
- package/public/vendor/dayjs/utils.js +58 -0
- package/public/vendor/htm.d.ts +1 -0
- package/public/vendor/htm.js +1 -0
- package/public/vendor/preact-hooks.d.ts +1 -0
- package/public/vendor/preact-hooks.js +2 -0
- package/public/vendor/preact.d.ts +1 -0
- package/public/vendor/preact.js +2 -0
- package/public/vendor/signals-core.d.ts +1 -0
- package/public/vendor/signals-core.js +1 -0
- package/public/vendor/signals.d.ts +1 -0
- package/public/vendor/signals.js +1 -0
- package/public/vendor/snarkdown.d.ts +1 -0
- package/public/vendor/snarkdown.js +2 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogsRepo = void 0;
|
|
4
|
+
const LOG_COLUMNS = `l.id, l.task_id, l.maintenance_date, l.runtime_hours, l.notes,
|
|
5
|
+
l.logged_by, l.created_at`;
|
|
6
|
+
class LogsRepo {
|
|
7
|
+
db;
|
|
8
|
+
constructor(db) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
}
|
|
11
|
+
insert(entry, nowIso) {
|
|
12
|
+
const result = this.db
|
|
13
|
+
.prepare(`INSERT INTO log_entries (task_id, maintenance_date, runtime_hours, notes, logged_by, created_at)
|
|
14
|
+
VALUES (?, ?, ?, ?, ?, ?)`)
|
|
15
|
+
.run(entry.task_id, entry.maintenance_date, entry.runtime_hours, entry.notes, entry.logged_by, nowIso);
|
|
16
|
+
return this.get(Number(result.lastInsertRowid));
|
|
17
|
+
}
|
|
18
|
+
get(id) {
|
|
19
|
+
return this.db
|
|
20
|
+
.prepare(`SELECT ${LOG_COLUMNS} FROM log_entries l WHERE l.id = ?`)
|
|
21
|
+
.get(id);
|
|
22
|
+
}
|
|
23
|
+
update(id, fields) {
|
|
24
|
+
this.db
|
|
25
|
+
.prepare(`UPDATE log_entries SET maintenance_date = ?, runtime_hours = ?, notes = ? WHERE id = ?`)
|
|
26
|
+
.run(fields.maintenance_date, fields.runtime_hours, fields.notes, id);
|
|
27
|
+
}
|
|
28
|
+
delete(id) {
|
|
29
|
+
this.db.prepare(`DELETE FROM log_entries WHERE id = ?`).run(id);
|
|
30
|
+
}
|
|
31
|
+
latestForTask(taskId) {
|
|
32
|
+
return this.db
|
|
33
|
+
.prepare(`SELECT ${LOG_COLUMNS} FROM log_entries l
|
|
34
|
+
WHERE l.task_id = ? ORDER BY l.maintenance_date DESC, l.id DESC LIMIT 1`)
|
|
35
|
+
.get(taskId);
|
|
36
|
+
}
|
|
37
|
+
listForTask(taskId) {
|
|
38
|
+
return this.db
|
|
39
|
+
.prepare(`SELECT ${LOG_COLUMNS} FROM log_entries l
|
|
40
|
+
WHERE l.task_id = ? ORDER BY l.maintenance_date DESC, l.id DESC`)
|
|
41
|
+
.all(taskId);
|
|
42
|
+
}
|
|
43
|
+
/** Task ids that have at least one log note matching the LIKE pattern. */
|
|
44
|
+
taskIdsWithNotesLike(pattern) {
|
|
45
|
+
const rows = this.db
|
|
46
|
+
.prepare(`SELECT DISTINCT task_id AS id FROM log_entries WHERE notes LIKE ?`)
|
|
47
|
+
.all(pattern);
|
|
48
|
+
return new Set(rows.map((r) => r.id));
|
|
49
|
+
}
|
|
50
|
+
count() {
|
|
51
|
+
const row = this.db
|
|
52
|
+
.prepare(`SELECT COUNT(*) AS n FROM log_entries`)
|
|
53
|
+
.get();
|
|
54
|
+
return row.n;
|
|
55
|
+
}
|
|
56
|
+
countForTask(taskId) {
|
|
57
|
+
const row = this.db
|
|
58
|
+
.prepare(`SELECT COUNT(*) AS n FROM log_entries WHERE task_id = ?`)
|
|
59
|
+
.get(taskId);
|
|
60
|
+
return row.n;
|
|
61
|
+
}
|
|
62
|
+
listMaster(q) {
|
|
63
|
+
const where = [];
|
|
64
|
+
const params = [];
|
|
65
|
+
if (q.search) {
|
|
66
|
+
const like = `%${q.search}%`;
|
|
67
|
+
where.push(`(l.notes LIKE ? OR t.name LIKE ? OR l.logged_by LIKE ?)`);
|
|
68
|
+
params.push(like, like, like);
|
|
69
|
+
}
|
|
70
|
+
const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : '';
|
|
71
|
+
const sortCol = q.sort === 'task'
|
|
72
|
+
? 't.name COLLATE NOCASE'
|
|
73
|
+
: q.sort === 'runtime_hours'
|
|
74
|
+
? 'l.runtime_hours'
|
|
75
|
+
: 'l.maintenance_date';
|
|
76
|
+
const orderSql = q.order === 'asc' ? 'ASC' : 'DESC';
|
|
77
|
+
const totalRow = this.db
|
|
78
|
+
.prepare(`SELECT COUNT(*) AS n FROM log_entries l JOIN tasks t ON t.id = l.task_id ${whereSql}`)
|
|
79
|
+
.get(...params);
|
|
80
|
+
const data = this.db
|
|
81
|
+
.prepare(`SELECT ${LOG_COLUMNS}, t.slug AS task_slug, t.name AS task_name
|
|
82
|
+
FROM log_entries l JOIN tasks t ON t.id = l.task_id
|
|
83
|
+
${whereSql}
|
|
84
|
+
ORDER BY ${sortCol} ${orderSql}, l.id DESC
|
|
85
|
+
LIMIT ? OFFSET ?`)
|
|
86
|
+
.all(...params, q.pageSize, (q.page - 1) * q.pageSize);
|
|
87
|
+
return { data, total: totalRow.n };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.LogsRepo = LogsRepo;
|
|
91
|
+
//# sourceMappingURL=logs.repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.repo.js","sourceRoot":"","sources":["../../src/db/logs.repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrations = void 0;
|
|
4
|
+
exports.migrations = [
|
|
5
|
+
{
|
|
6
|
+
version: 1,
|
|
7
|
+
up(db) {
|
|
8
|
+
db.exec(`
|
|
9
|
+
CREATE TABLE tasks (
|
|
10
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11
|
+
slug TEXT NOT NULL UNIQUE,
|
|
12
|
+
name TEXT NOT NULL,
|
|
13
|
+
description TEXT,
|
|
14
|
+
runtime_interval REAL,
|
|
15
|
+
time_interval INTEGER,
|
|
16
|
+
time_interval_unit TEXT CHECK (time_interval_unit IN ('days','weeks','months','years')),
|
|
17
|
+
runtime_path TEXT,
|
|
18
|
+
last_maintenance TEXT,
|
|
19
|
+
last_runtime REAL,
|
|
20
|
+
seed_last_maintenance TEXT,
|
|
21
|
+
seed_last_runtime REAL,
|
|
22
|
+
created_at TEXT NOT NULL,
|
|
23
|
+
updated_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE tags (
|
|
27
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28
|
+
name TEXT NOT NULL COLLATE NOCASE UNIQUE
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE task_tags (
|
|
32
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
33
|
+
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
|
|
34
|
+
PRIMARY KEY (task_id, tag_id)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE log_entries (
|
|
38
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
39
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
40
|
+
maintenance_date TEXT NOT NULL,
|
|
41
|
+
runtime_hours REAL,
|
|
42
|
+
notes TEXT,
|
|
43
|
+
logged_by TEXT,
|
|
44
|
+
created_at TEXT NOT NULL
|
|
45
|
+
);
|
|
46
|
+
CREATE INDEX idx_log_task_date ON log_entries (task_id, maintenance_date DESC);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE runtime_cache (
|
|
49
|
+
path TEXT PRIMARY KEY,
|
|
50
|
+
value REAL NOT NULL,
|
|
51
|
+
timestamp TEXT NOT NULL
|
|
52
|
+
);
|
|
53
|
+
`);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":";;;AAOa,QAAA,UAAU,GAAgB;IACrC;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CP,CAAC,CAAC;QACL,CAAC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TagsRepo = void 0;
|
|
4
|
+
class TagsRepo {
|
|
5
|
+
db;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
/** Find (case-insensitively) or create a tag; returns its id. */
|
|
10
|
+
getOrCreate(name) {
|
|
11
|
+
const trimmed = name.trim();
|
|
12
|
+
const existing = this.db
|
|
13
|
+
.prepare(`SELECT id FROM tags WHERE name = ?`)
|
|
14
|
+
.get(trimmed);
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing.id;
|
|
17
|
+
const result = this.db
|
|
18
|
+
.prepare(`INSERT INTO tags (name) VALUES (?)`)
|
|
19
|
+
.run(trimmed);
|
|
20
|
+
return Number(result.lastInsertRowid);
|
|
21
|
+
}
|
|
22
|
+
/** Replace a task's tag set; creates new tags on demand and prunes orphans. */
|
|
23
|
+
setTaskTags(taskId, names) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const ids = [];
|
|
26
|
+
for (const raw of names) {
|
|
27
|
+
const name = raw.trim();
|
|
28
|
+
if (!name)
|
|
29
|
+
continue;
|
|
30
|
+
const key = name.toLowerCase();
|
|
31
|
+
if (seen.has(key))
|
|
32
|
+
continue;
|
|
33
|
+
seen.add(key);
|
|
34
|
+
ids.push(this.getOrCreate(name));
|
|
35
|
+
}
|
|
36
|
+
this.db.prepare(`DELETE FROM task_tags WHERE task_id = ?`).run(taskId);
|
|
37
|
+
const insert = this.db.prepare(`INSERT INTO task_tags (task_id, tag_id) VALUES (?, ?)`);
|
|
38
|
+
for (const tagId of ids)
|
|
39
|
+
insert.run(taskId, tagId);
|
|
40
|
+
this.pruneOrphans();
|
|
41
|
+
}
|
|
42
|
+
tagsForTask(taskId) {
|
|
43
|
+
const rows = this.db
|
|
44
|
+
.prepare(`SELECT t.name AS name FROM tags t
|
|
45
|
+
JOIN task_tags tt ON tt.tag_id = t.id
|
|
46
|
+
WHERE tt.task_id = ? ORDER BY t.name COLLATE NOCASE`)
|
|
47
|
+
.all(taskId);
|
|
48
|
+
return rows.map((r) => r.name);
|
|
49
|
+
}
|
|
50
|
+
/** One query for the whole task list: task_id -> tag names. */
|
|
51
|
+
tagsByTask() {
|
|
52
|
+
const rows = this.db
|
|
53
|
+
.prepare(`SELECT tt.task_id AS task_id, t.name AS name FROM tags t
|
|
54
|
+
JOIN task_tags tt ON tt.tag_id = t.id
|
|
55
|
+
ORDER BY t.name COLLATE NOCASE`)
|
|
56
|
+
.all();
|
|
57
|
+
const map = new Map();
|
|
58
|
+
for (const r of rows) {
|
|
59
|
+
const list = map.get(r.task_id) ?? [];
|
|
60
|
+
list.push(r.name);
|
|
61
|
+
map.set(r.task_id, list);
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
listWithCounts() {
|
|
66
|
+
return this.db
|
|
67
|
+
.prepare(`SELECT t.id AS id, t.name AS name, COUNT(tt.task_id) AS count
|
|
68
|
+
FROM tags t LEFT JOIN task_tags tt ON tt.tag_id = t.id
|
|
69
|
+
GROUP BY t.id ORDER BY t.name COLLATE NOCASE`)
|
|
70
|
+
.all();
|
|
71
|
+
}
|
|
72
|
+
/** Remove tags no task references (§5.2). */
|
|
73
|
+
pruneOrphans() {
|
|
74
|
+
this.db.exec(`DELETE FROM tags WHERE id NOT IN (SELECT DISTINCT tag_id FROM task_tags)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.TagsRepo = TagsRepo;
|
|
78
|
+
//# sourceMappingURL=tags.repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.repo.js","sourceRoot":"","sources":["../../src/db/tags.repo.ts"],"names":[],"mappings":";;;AAQA,MAAa,QAAQ;IACC;IAApB,YAAoB,EAAgB;QAAhB,OAAE,GAAF,EAAE,CAAc;IAAG,CAAC;IAExC,iEAAiE;IACjE,WAAW,CAAC,IAAY;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE;aACrB,OAAO,CAAC,oCAAoC,CAAC;aAC7C,GAAG,CAAC,OAAO,CAA+B,CAAC;QAC9C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE;aACnB,OAAO,CAAC,oCAAoC,CAAC;aAC7C,GAAG,CAAC,OAAO,CAAC,CAAC;QAChB,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACxC,CAAC;IAED,+EAA+E;IAC/E,WAAW,CAAC,MAAc,EAAE,KAAe;QACzC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvE,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC5B,uDAAuD,CACxD,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,GAAG;YAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,MAAc;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN;;6DAEqD,CACtD;aACA,GAAG,CAAC,MAAM,CAAkC,CAAC;QAChD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,+DAA+D;IAC/D,UAAU;QACR,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN;;wCAEgC,CACjC;aACA,GAAG,EAAoD,CAAC;QAC3D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACtC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAClB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,EAAE;aACX,OAAO,CACN;;sDAE8C,CAC/C;aACA,GAAG,EAA2B,CAAC;IACpC,CAAC;IAED,6CAA6C;IAC7C,YAAY;QACV,IAAI,CAAC,EAAE,CAAC,IAAI,CACV,0EAA0E,CAC3E,CAAC;IACJ,CAAC;CACF;AAjFD,4BAiFC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TasksRepo = void 0;
|
|
4
|
+
const COLUMNS = `id, slug, name, description, runtime_interval, time_interval,
|
|
5
|
+
time_interval_unit, runtime_path, last_maintenance, last_runtime,
|
|
6
|
+
seed_last_maintenance, seed_last_runtime, created_at, updated_at`;
|
|
7
|
+
class TasksRepo {
|
|
8
|
+
db;
|
|
9
|
+
constructor(db) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
}
|
|
12
|
+
create(t, nowIso) {
|
|
13
|
+
const result = this.db
|
|
14
|
+
.prepare(`INSERT INTO tasks (slug, name, description, runtime_interval, time_interval,
|
|
15
|
+
time_interval_unit, runtime_path, last_maintenance, last_runtime,
|
|
16
|
+
seed_last_maintenance, seed_last_runtime, created_at, updated_at)
|
|
17
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
18
|
+
.run(t.slug, t.name, t.description, t.runtime_interval, t.time_interval, t.time_interval_unit, t.runtime_path, t.last_maintenance, t.last_runtime, t.seed_last_maintenance, t.seed_last_runtime, nowIso, nowIso);
|
|
19
|
+
return this.getById(Number(result.lastInsertRowid));
|
|
20
|
+
}
|
|
21
|
+
getById(id) {
|
|
22
|
+
return this.db
|
|
23
|
+
.prepare(`SELECT ${COLUMNS} FROM tasks WHERE id = ?`)
|
|
24
|
+
.get(id);
|
|
25
|
+
}
|
|
26
|
+
getBySlug(slug) {
|
|
27
|
+
return this.db
|
|
28
|
+
.prepare(`SELECT ${COLUMNS} FROM tasks WHERE slug = ?`)
|
|
29
|
+
.get(slug);
|
|
30
|
+
}
|
|
31
|
+
listAll() {
|
|
32
|
+
return this.db
|
|
33
|
+
.prepare(`SELECT ${COLUMNS} FROM tasks ORDER BY name COLLATE NOCASE`)
|
|
34
|
+
.all();
|
|
35
|
+
}
|
|
36
|
+
slugExists(slug, excludeId) {
|
|
37
|
+
const row = excludeId != null
|
|
38
|
+
? this.db
|
|
39
|
+
.prepare(`SELECT 1 AS x FROM tasks WHERE slug = ? AND id != ?`)
|
|
40
|
+
.get(slug, excludeId)
|
|
41
|
+
: this.db.prepare(`SELECT 1 AS x FROM tasks WHERE slug = ?`).get(slug);
|
|
42
|
+
return row !== undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Full-row update of the mutable columns (service layer merges partial input
|
|
46
|
+
* into the loaded row before calling this).
|
|
47
|
+
*/
|
|
48
|
+
update(id, t, nowIso) {
|
|
49
|
+
this.db
|
|
50
|
+
.prepare(`UPDATE tasks SET slug = ?, name = ?, description = ?, runtime_interval = ?,
|
|
51
|
+
time_interval = ?, time_interval_unit = ?, runtime_path = ?,
|
|
52
|
+
last_maintenance = ?, last_runtime = ?,
|
|
53
|
+
seed_last_maintenance = ?, seed_last_runtime = ?, updated_at = ?
|
|
54
|
+
WHERE id = ?`)
|
|
55
|
+
.run(t.slug, t.name, t.description, t.runtime_interval, t.time_interval, t.time_interval_unit, t.runtime_path, t.last_maintenance, t.last_runtime, t.seed_last_maintenance, t.seed_last_runtime, nowIso, id);
|
|
56
|
+
}
|
|
57
|
+
setLast(id, lastMaintenance, lastRuntime) {
|
|
58
|
+
this.db
|
|
59
|
+
.prepare(`UPDATE tasks SET last_maintenance = ?, last_runtime = ? WHERE id = ?`)
|
|
60
|
+
.run(lastMaintenance, lastRuntime, id);
|
|
61
|
+
}
|
|
62
|
+
delete(id) {
|
|
63
|
+
this.db.prepare(`DELETE FROM tasks WHERE id = ?`).run(id);
|
|
64
|
+
}
|
|
65
|
+
count() {
|
|
66
|
+
const row = this.db.prepare(`SELECT COUNT(*) AS n FROM tasks`).get();
|
|
67
|
+
return row.n;
|
|
68
|
+
}
|
|
69
|
+
/** Distinct non-null runtime paths across all tasks (drives subscriptions). */
|
|
70
|
+
runtimePaths() {
|
|
71
|
+
const rows = this.db
|
|
72
|
+
.prepare(`SELECT DISTINCT runtime_path AS p FROM tasks WHERE runtime_path IS NOT NULL AND runtime_path != ''`)
|
|
73
|
+
.all();
|
|
74
|
+
return rows.map((r) => r.p);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.TasksRepo = TasksRepo;
|
|
78
|
+
//# sourceMappingURL=tasks.repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.repo.js","sourceRoot":"","sources":["../../src/db/tasks.repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slugify = slugify;
|
|
4
|
+
exports.uniqueSlug = uniqueSlug;
|
|
5
|
+
function slugify(input) {
|
|
6
|
+
const slug = input
|
|
7
|
+
.normalize('NFKD')
|
|
8
|
+
.replace(/[̀-ͯ]/g, '') // strip diacritics after decomposition
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
11
|
+
.replace(/-{2,}/g, '-')
|
|
12
|
+
.replace(/^-+|-+$/g, '');
|
|
13
|
+
return slug || 'task';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Make `base` unique by appending -2, -3, … until `exists` returns false.
|
|
17
|
+
*/
|
|
18
|
+
function uniqueSlug(base, exists) {
|
|
19
|
+
if (!exists(base))
|
|
20
|
+
return base;
|
|
21
|
+
for (let n = 2;; n++) {
|
|
22
|
+
const candidate = `${base}-${n}`;
|
|
23
|
+
if (!exists(candidate))
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/domain/slug.ts"],"names":[],"mappings":";;AAAA,0BASC;AAKD,gCASC;AAvBD,SAAgB,OAAO,CAAC,KAAa;IACnC,MAAM,IAAI,GAAG,KAAK;SACf,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,uCAAuC;SAC7D,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,IAAI,IAAI,MAAM,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,SAAgB,UAAU,CACxB,IAAY,EACZ,MAAiC;IAEjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,EAAE,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC3C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addInterval = addInterval;
|
|
4
|
+
exports.computeTask = computeTask;
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const MS_PER_DAY = 86_400_000;
|
|
7
|
+
/**
|
|
8
|
+
* Calendar-aware interval addition (UTC). Month/year arithmetic clamps to the
|
|
9
|
+
* end of the target month (Jan 31 + 1 month = Feb 28/29), matching day.js
|
|
10
|
+
* `.add` semantics used on the frontend.
|
|
11
|
+
*/
|
|
12
|
+
function addInterval(iso, n, unit) {
|
|
13
|
+
const d = new Date(iso);
|
|
14
|
+
switch (unit) {
|
|
15
|
+
case 'days':
|
|
16
|
+
d.setUTCDate(d.getUTCDate() + n);
|
|
17
|
+
return d;
|
|
18
|
+
case 'weeks':
|
|
19
|
+
d.setUTCDate(d.getUTCDate() + 7 * n);
|
|
20
|
+
return d;
|
|
21
|
+
case 'months':
|
|
22
|
+
return addMonths(d, n);
|
|
23
|
+
case 'years':
|
|
24
|
+
return addMonths(d, 12 * n);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function addMonths(d, n) {
|
|
28
|
+
const r = new Date(d.getTime());
|
|
29
|
+
const day = r.getUTCDate();
|
|
30
|
+
r.setUTCDate(1);
|
|
31
|
+
r.setUTCMonth(r.getUTCMonth() + n);
|
|
32
|
+
const daysInTarget = new Date(Date.UTC(r.getUTCFullYear(), r.getUTCMonth() + 1, 0)).getUTCDate();
|
|
33
|
+
r.setUTCDate(Math.min(day, daysInTarget));
|
|
34
|
+
return r;
|
|
35
|
+
}
|
|
36
|
+
function mostUrgent(statuses) {
|
|
37
|
+
if (statuses.length === 0)
|
|
38
|
+
return 'unknown';
|
|
39
|
+
return statuses.reduce((a, b) => (types_1.STATUS_RANK[a] <= types_1.STATUS_RANK[b] ? a : b));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compute all derived fields for a task (§6.2/§6.3). Pure function: runtime
|
|
43
|
+
* value and clock are injected.
|
|
44
|
+
*/
|
|
45
|
+
function computeTask(task, currentRuntime, now, cfg) {
|
|
46
|
+
const out = {
|
|
47
|
+
current_runtime: currentRuntime,
|
|
48
|
+
elapsed_runtime: null,
|
|
49
|
+
remaining_runtime: null,
|
|
50
|
+
due_runtime_at: null,
|
|
51
|
+
runtime_fraction: null,
|
|
52
|
+
due_date: null,
|
|
53
|
+
remaining_time_ms: null,
|
|
54
|
+
time_fraction: null,
|
|
55
|
+
runtime_status: null,
|
|
56
|
+
time_status: null,
|
|
57
|
+
status: 'unknown',
|
|
58
|
+
status_rank: types_1.STATUS_RANK.unknown,
|
|
59
|
+
urgency: -Infinity,
|
|
60
|
+
};
|
|
61
|
+
// Runtime dimension: configured when both interval and path are set.
|
|
62
|
+
if (task.runtime_interval != null && task.runtime_path != null) {
|
|
63
|
+
if (task.last_runtime != null && currentRuntime != null) {
|
|
64
|
+
const elapsed = currentRuntime - task.last_runtime;
|
|
65
|
+
const remaining = task.runtime_interval - elapsed;
|
|
66
|
+
out.elapsed_runtime = elapsed;
|
|
67
|
+
out.remaining_runtime = remaining;
|
|
68
|
+
out.due_runtime_at = task.last_runtime + task.runtime_interval;
|
|
69
|
+
out.runtime_fraction = elapsed / task.runtime_interval;
|
|
70
|
+
out.runtime_status =
|
|
71
|
+
remaining <= 0
|
|
72
|
+
? 'overdue'
|
|
73
|
+
: remaining <= cfg.runtimeNotifyLeadHours
|
|
74
|
+
? 'due_soon'
|
|
75
|
+
: 'ok';
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
out.runtime_status = 'unknown';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Time dimension: configured when interval magnitude + unit are set.
|
|
82
|
+
if (task.time_interval != null && task.time_interval_unit != null) {
|
|
83
|
+
if (task.last_maintenance != null) {
|
|
84
|
+
const last = new Date(task.last_maintenance);
|
|
85
|
+
const due = addInterval(task.last_maintenance, task.time_interval, task.time_interval_unit);
|
|
86
|
+
const remainingMs = due.getTime() - now.getTime();
|
|
87
|
+
out.due_date = due.toISOString();
|
|
88
|
+
out.remaining_time_ms = remainingMs;
|
|
89
|
+
const span = due.getTime() - last.getTime();
|
|
90
|
+
out.time_fraction =
|
|
91
|
+
span > 0 ? (now.getTime() - last.getTime()) / span : 1;
|
|
92
|
+
out.time_status =
|
|
93
|
+
remainingMs <= 0
|
|
94
|
+
? 'overdue'
|
|
95
|
+
: remainingMs <= cfg.timeNotifyLeadDays * MS_PER_DAY
|
|
96
|
+
? 'due_soon'
|
|
97
|
+
: 'ok';
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
out.time_status = 'unknown';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const dims = [out.runtime_status, out.time_status].filter((s) => s != null);
|
|
104
|
+
out.status = mostUrgent(dims);
|
|
105
|
+
out.status_rank = types_1.STATUS_RANK[out.status];
|
|
106
|
+
const fractions = [out.runtime_fraction, out.time_fraction].filter((f) => f != null);
|
|
107
|
+
out.urgency = fractions.length ? Math.max(...fractions) : -Infinity;
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/domain/status.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
const path = __importStar(require("node:path"));
|
|
36
|
+
const config_1 = require("./config");
|
|
37
|
+
const database_1 = require("./db/database");
|
|
38
|
+
const service_1 = require("./service");
|
|
39
|
+
const notifications_1 = require("./signalk/notifications");
|
|
40
|
+
const runtime_1 = require("./signalk/runtime");
|
|
41
|
+
const router_1 = require("./api/router");
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
+
const { version: PLUGIN_VERSION } = require('../package.json');
|
|
44
|
+
const PLUGIN_ID = 'signalk-maintenance-tracker';
|
|
45
|
+
module.exports = function (app) {
|
|
46
|
+
let db = null;
|
|
47
|
+
let runtime = null;
|
|
48
|
+
let notifier = null;
|
|
49
|
+
let service = null;
|
|
50
|
+
let timer = null;
|
|
51
|
+
let services = null;
|
|
52
|
+
function recomputeNotifications() {
|
|
53
|
+
if (!service || !notifier)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
notifier.publishAll(service.listAllComputed());
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
app.error?.(`${PLUGIN_ID}: notification recompute failed: ${err}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function refresh() {
|
|
63
|
+
if (!service || !runtime)
|
|
64
|
+
return;
|
|
65
|
+
runtime.setPaths(service.runtimePaths());
|
|
66
|
+
recomputeNotifications();
|
|
67
|
+
}
|
|
68
|
+
const plugin = {
|
|
69
|
+
id: PLUGIN_ID,
|
|
70
|
+
name: 'Maintenance Tracker',
|
|
71
|
+
description: 'Track recurring boat maintenance tasks with runtime- and time-based intervals.',
|
|
72
|
+
schema: config_1.schema,
|
|
73
|
+
start(options) {
|
|
74
|
+
const opts = (0, config_1.withDefaults)(options);
|
|
75
|
+
try {
|
|
76
|
+
const dbPath = path.join(app.getDataDirPath(), 'maintenance.db');
|
|
77
|
+
db = (0, database_1.openDatabase)(dbPath);
|
|
78
|
+
runtime = new runtime_1.RuntimeManager(app, db);
|
|
79
|
+
notifier = new notifications_1.NotificationManager(app, PLUGIN_ID, opts);
|
|
80
|
+
service = new service_1.MaintenanceService(db, {
|
|
81
|
+
getRuntime: (p) => runtime.getHours(p),
|
|
82
|
+
config: {
|
|
83
|
+
runtimeNotifyLeadHours: opts.runtimeNotifyLeadHours,
|
|
84
|
+
timeNotifyLeadDays: opts.timeNotifyLeadDays,
|
|
85
|
+
},
|
|
86
|
+
onMutation: (event) => {
|
|
87
|
+
if (event.clearedSlug)
|
|
88
|
+
notifier?.clear(event.clearedSlug);
|
|
89
|
+
refresh();
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
runtime.onUpdate(() => recomputeNotifications());
|
|
93
|
+
services = { service, runtime, version: PLUGIN_VERSION };
|
|
94
|
+
refresh();
|
|
95
|
+
timer = setInterval(recomputeNotifications, opts.recomputeIntervalMs);
|
|
96
|
+
app.setPluginStatus?.(`Started — ${service.health().tasks} tasks, DB at ${dbPath}`);
|
|
97
|
+
app.debug?.(`${PLUGIN_ID} started`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
app.setPluginError?.(`Failed to start: ${err}`);
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
stop() {
|
|
105
|
+
if (timer) {
|
|
106
|
+
clearInterval(timer);
|
|
107
|
+
timer = null;
|
|
108
|
+
}
|
|
109
|
+
runtime?.stop();
|
|
110
|
+
services = null;
|
|
111
|
+
service = null;
|
|
112
|
+
notifier = null;
|
|
113
|
+
runtime = null;
|
|
114
|
+
try {
|
|
115
|
+
db?.close();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// already closed
|
|
119
|
+
}
|
|
120
|
+
db = null;
|
|
121
|
+
app.debug?.(`${PLUGIN_ID} stopped`);
|
|
122
|
+
},
|
|
123
|
+
registerWithRouter(router) {
|
|
124
|
+
// May be called before start(); handlers respond 503 until started.
|
|
125
|
+
(0, router_1.mountApi)(router, () => services);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
return plugin;
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|