iobroker.time-tracker 0.7.1 → 0.7.3
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/README.md +63 -22
- package/build/lib/adapter/commands.js +12 -0
- package/build/lib/adapter/commands.js.map +2 -2
- package/build/lib/adapter/stateNames.js +84 -0
- package/build/lib/adapter/stateNames.js.map +2 -2
- package/build/lib/adapter/states.js +44 -1
- package/build/lib/adapter/states.js.map +2 -2
- package/build/lib/reports/pdf.js +8 -2
- package/build/lib/reports/pdf.js.map +2 -2
- package/build/lib/services/calendar.js +125 -0
- package/build/lib/services/calendar.js.map +7 -0
- package/build/lib/web/api.js +44 -67
- package/build/lib/web/api.js.map +2 -2
- package/build/lib/web/problem.js.map +1 -1
- package/build/main.js +44 -1
- package/build/main.js.map +2 -2
- package/io-package.json +27 -27
- package/package.json +1 -1
- package/src-pwa/src/i18n/de.json +5 -1
- package/src-pwa/src/i18n/en.json +5 -1
- package/src-pwa/src/i18n/es.json +5 -1
- package/src-pwa/src/i18n/fr.json +5 -1
- package/src-pwa/src/i18n/it.json +5 -1
- package/src-pwa/src/i18n/nl.json +5 -1
- package/src-pwa/src/i18n/pl.json +5 -1
- package/src-pwa/src/i18n/pt.json +5 -1
- package/src-pwa/src/i18n/ru.json +5 -1
- package/src-pwa/src/i18n/uk.json +5 -1
- package/src-pwa/src/i18n/zh-cn.json +5 -1
- package/www/assets/{index-D5fSC8CA.js → index-DB_lTeVU.js} +3 -3
- package/www/assets/index-DB_lTeVU.js.map +1 -0
- package/www/index.html +1 -1
- package/www/sw.js +1 -1
- package/www/sw.js.map +1 -1
- package/www/assets/index-D5fSC8CA.js.map +0 -1
package/build/lib/web/api.js
CHANGED
|
@@ -38,6 +38,7 @@ var import_absences = require("../db/repositories/absences");
|
|
|
38
38
|
var import_audit = require("../db/repositories/audit");
|
|
39
39
|
var import_rfid = require("../db/repositories/rfid");
|
|
40
40
|
var import_auth = require("../services/auth");
|
|
41
|
+
var import_calendar = require("../services/calendar");
|
|
41
42
|
var import_errors = require("../errors");
|
|
42
43
|
var import_seed = require("../db/seed");
|
|
43
44
|
var import_punch = require("../domain/punch");
|
|
@@ -377,20 +378,16 @@ function createApi(deps) {
|
|
|
377
378
|
const emit = (event) => {
|
|
378
379
|
events.publish({ ...event, atUtc: now() });
|
|
379
380
|
};
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
if (!actor || actor.user.id !== ownerId || actor.permissions.includes("time.edit_other")) {
|
|
381
|
+
function requireActiveAdministrator(userId, next) {
|
|
382
|
+
if (next.isActive && next.roleKeys.includes("admin")) {
|
|
383
383
|
return;
|
|
384
384
|
}
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (tsUtc < now() - days * 86400) {
|
|
385
|
+
const remaining = users.list().some((user) => user.id !== userId && users.roles(user.id).includes("admin"));
|
|
386
|
+
if (!remaining) {
|
|
390
387
|
throw (0, import_problem.problem)(
|
|
391
|
-
|
|
392
|
-
"
|
|
393
|
-
|
|
388
|
+
409,
|
|
389
|
+
"last_administrator",
|
|
390
|
+
"the last active administrator cannot be deactivated or lose the admin role"
|
|
394
391
|
);
|
|
395
392
|
}
|
|
396
393
|
}
|
|
@@ -771,7 +768,6 @@ function createApi(deps) {
|
|
|
771
768
|
if (tsUtc === null) {
|
|
772
769
|
throw new import_errors.ValidationError("tsUtc is required");
|
|
773
770
|
}
|
|
774
|
-
requireInsideEditWindow(context, target, tsUtc);
|
|
775
771
|
const timestamp = now();
|
|
776
772
|
const stored = entries.insert({
|
|
777
773
|
userId: target,
|
|
@@ -805,44 +801,11 @@ function createApi(deps) {
|
|
|
805
801
|
{ location: `/entries/${stored.entry.id}` }
|
|
806
802
|
);
|
|
807
803
|
});
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const [year, month, day] = date.split("-").map(Number);
|
|
814
|
-
return new Date(Date.UTC(year, month - 1, day + 1)).toISOString().slice(0, 10);
|
|
815
|
-
};
|
|
816
|
-
const calendarDocument = (user, list, stamp) => {
|
|
817
|
-
const lines = [
|
|
818
|
-
"BEGIN:VCALENDAR",
|
|
819
|
-
"VERSION:2.0",
|
|
820
|
-
"PRODID:-//ioBroker//time-tracker//EN",
|
|
821
|
-
"CALSCALE:GREGORIAN",
|
|
822
|
-
"METHOD:PUBLISH",
|
|
823
|
-
`X-WR-CALNAME:${icalText(`Abwesenheiten ${user.displayName}`)}`,
|
|
824
|
-
`REFRESH-INTERVAL;VALUE=DURATION:${CALENDAR_TTL}`,
|
|
825
|
-
`X-PUBLISHED-TTL:${CALENDAR_TTL}`
|
|
826
|
-
];
|
|
827
|
-
for (const absence of list) {
|
|
828
|
-
const portion = absence.dayPortion < 1 ? ` (${absence.dayPortion})` : "";
|
|
829
|
-
lines.push(
|
|
830
|
-
"BEGIN:VEVENT",
|
|
831
|
-
`UID:absence-${absence.id}@time-tracker`,
|
|
832
|
-
`DTSTAMP:${icalStamp(stamp)}`,
|
|
833
|
-
`DTSTART;VALUE=DATE:${absence.dateFrom.replace(/-/g, "")}`,
|
|
834
|
-
`DTEND;VALUE=DATE:${dayAfter(absence.dateTo).replace(/-/g, "")}`,
|
|
835
|
-
`SUMMARY:${icalText(`${absence.typeName} (${absence.typeCode})${portion}`)}`,
|
|
836
|
-
absence.approval === "approved" ? "STATUS:CONFIRMED" : "STATUS:TENTATIVE",
|
|
837
|
-
absence.note ? `DESCRIPTION:${icalText(absence.note)}` : null,
|
|
838
|
-
"TRANSP:TRANSPARENT",
|
|
839
|
-
"END:VEVENT"
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
lines.push("END:VCALENDAR");
|
|
843
|
-
return `${lines.filter((line) => line !== null).join("\r\n")}\r
|
|
844
|
-
`;
|
|
845
|
-
};
|
|
804
|
+
const calendarEmployee = (user) => ({
|
|
805
|
+
id: user.id,
|
|
806
|
+
displayName: user.displayName,
|
|
807
|
+
login: user.login
|
|
808
|
+
});
|
|
846
809
|
const publicAbsence = (absence) => {
|
|
847
810
|
var _a2, _b2;
|
|
848
811
|
const type = absences.findType(absence.typeId);
|
|
@@ -1052,18 +1015,29 @@ function createApi(deps) {
|
|
|
1052
1015
|
(context) => {
|
|
1053
1016
|
var _a2;
|
|
1054
1017
|
const token = ((_a2 = context.query("token")) != null ? _a2 : "").trim();
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1018
|
+
const instanceToken = settings.get("calendar_token");
|
|
1019
|
+
const company = instanceToken !== null && instanceToken !== "" && token === instanceToken;
|
|
1020
|
+
const user = company || token === "" ? null : users.findByCalendarToken(token);
|
|
1021
|
+
if (!company && (!user || !user.isActive)) {
|
|
1057
1022
|
throw (0, import_problem.problem)(401, "invalid_credentials", "the calendar token is not valid");
|
|
1058
1023
|
}
|
|
1059
1024
|
const stamp = now();
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}
|
|
1025
|
+
const year = new Date(stamp * 1e3).getUTCFullYear();
|
|
1026
|
+
const from = `${year - 1}-01-01`;
|
|
1027
|
+
const to = `${year + 1}-12-31`;
|
|
1028
|
+
const owners = user ? [user] : users.list();
|
|
1029
|
+
const employees = new Map(owners.map((employee) => [employee.id, calendarEmployee(employee)]));
|
|
1030
|
+
const list = user ? absences.withTypesInRange(user.id, from, to) : absences.allInRange(from, to);
|
|
1031
|
+
const name = user ? `Abwesenheiten ${user.displayName}` : import_calendar.COMPANY_CALENDAR_NAME;
|
|
1032
|
+
return (0, import_router.binary)(
|
|
1033
|
+
200,
|
|
1034
|
+
Buffer.from((0, import_calendar.calendarDocument)(name, (0, import_calendar.absenceEvents)(list, employees, !user), stamp), "utf8"),
|
|
1035
|
+
import_calendar.ICAL_CONTENT_TYPE,
|
|
1036
|
+
{
|
|
1037
|
+
"content-disposition": 'inline; filename="time-tracker.ics"',
|
|
1038
|
+
"cache-control": "no-store"
|
|
1039
|
+
}
|
|
1040
|
+
);
|
|
1067
1041
|
}
|
|
1068
1042
|
);
|
|
1069
1043
|
route("POST", "/calendar/token", { permission: "report.view_own", csrf: true }, (context) => {
|
|
@@ -1497,10 +1471,6 @@ function createApi(deps) {
|
|
|
1497
1471
|
if (wantedTs !== null && !context.auth.permissions.includes("time.edit_other")) {
|
|
1498
1472
|
throw (0, import_problem.problem)(403, "permission_denied", "request rejected (permission_denied: time.edit_other)");
|
|
1499
1473
|
}
|
|
1500
|
-
if (wantedTs !== null) {
|
|
1501
|
-
requireInsideEditWindow(context, existing.userId, existing.tsUtc);
|
|
1502
|
-
requireInsideEditWindow(context, existing.userId, wantedTs);
|
|
1503
|
-
}
|
|
1504
1474
|
const target = users.findById(existing.userId);
|
|
1505
1475
|
if (!target) {
|
|
1506
1476
|
throw new import_errors.NotFoundError(`user ${existing.userId} not found`);
|
|
@@ -1702,12 +1672,13 @@ function createApi(deps) {
|
|
|
1702
1672
|
});
|
|
1703
1673
|
});
|
|
1704
1674
|
route("PATCH", "/users/:id", { permission: "user.edit", csrf: true }, (context) => {
|
|
1705
|
-
var _a2, _b2, _c2;
|
|
1675
|
+
var _a2, _b2, _c2, _d2;
|
|
1706
1676
|
if (!context.auth) {
|
|
1707
1677
|
throw (0, import_problem.problem)(401, "no_session", "request rejected (no_session)");
|
|
1708
1678
|
}
|
|
1709
1679
|
const id = numberParam(context, "id");
|
|
1710
|
-
|
|
1680
|
+
const target = users.findById(id);
|
|
1681
|
+
if (!target) {
|
|
1711
1682
|
throw new import_errors.NotFoundError(`user ${id} not found`);
|
|
1712
1683
|
}
|
|
1713
1684
|
const body = context.jsonBody();
|
|
@@ -1770,19 +1741,25 @@ function createApi(deps) {
|
|
|
1770
1741
|
now: now()
|
|
1771
1742
|
});
|
|
1772
1743
|
}
|
|
1744
|
+
if (patch.isActive === false || roleKeys !== null) {
|
|
1745
|
+
requireActiveAdministrator(id, {
|
|
1746
|
+
isActive: (_b2 = patch.isActive) != null ? _b2 : target.isActive,
|
|
1747
|
+
roleKeys: roleKeys != null ? roleKeys : users.roles(id)
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1773
1750
|
const updated = users.update({
|
|
1774
1751
|
id,
|
|
1775
1752
|
patch,
|
|
1776
1753
|
reason: optionalString(body, "reason"),
|
|
1777
1754
|
actorId: context.auth.user.id,
|
|
1778
|
-
actorIp: (
|
|
1755
|
+
actorIp: (_c2 = context.request.remoteAddress) != null ? _c2 : null,
|
|
1779
1756
|
now: now()
|
|
1780
1757
|
});
|
|
1781
1758
|
const roles = roleKeys ? users.setRoles({
|
|
1782
1759
|
userId: id,
|
|
1783
1760
|
roleKeys,
|
|
1784
1761
|
actorId: context.auth.user.id,
|
|
1785
|
-
actorIp: (
|
|
1762
|
+
actorIp: (_d2 = context.request.remoteAddress) != null ? _d2 : null,
|
|
1786
1763
|
now: now()
|
|
1787
1764
|
}) : users.roles(id);
|
|
1788
1765
|
return (0, import_router.json)(200, { user: { ...publicUser(updated), roles } });
|