From 5e017abcf1ce6b3529b952f34cc4bb923a9b1ddf Mon Sep 17 00:00:00 2001 From: benedict Date: Wed, 6 May 2026 03:18:53 +0700 Subject: [PATCH] initial version --- .gitignore | 2 + bun.lock | 225 ++++++++++++++++++++++++++ generateMonthlySummary.ts | 201 +++++++++++++++++++++++ package.json | 22 +++ templates/core/design-system.typ | 99 ++++++++++++ templates/core/lixusLogo.1012x362.png | Bin 0 -> 14130 bytes templates/workstation-summary.typ | 112 +++++++++++++ tsconfig.json | 16 ++ 8 files changed, 677 insertions(+) create mode 100644 .gitignore create mode 100644 bun.lock create mode 100644 generateMonthlySummary.ts create mode 100644 package.json create mode 100644 templates/core/design-system.typ create mode 100644 templates/core/lixusLogo.1012x362.png create mode 100644 templates/workstation-summary.typ create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab12c38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +reports diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..d4b2fe8 --- /dev/null +++ b/bun.lock @@ -0,0 +1,225 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "employee-workstation-report", + "dependencies": { + "dayjs": "^1.11.10", + "exceljs": "^4.4.0", + "fs-extra": "^11.2.0", + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "^20.11.0", + "bun-types": "latest", + "typescript": "^5.4.0", + }, + }, + }, + "packages": { + "@fast-csv/format": ["@fast-csv/format@4.3.5", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.isboolean": "^3.0.3", "lodash.isequal": "^4.5.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0" } }, "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A=="], + + "@fast-csv/parse": ["@fast-csv/parse@4.3.6", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.groupby": "^4.6.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0", "lodash.isundefined": "^3.0.1", "lodash.uniq": "^4.5.0" } }, "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA=="], + + "@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="], + + "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="], + + "@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="], + + "archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="], + + "archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], + + "binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="], + + "brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="], + + "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], + + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + + "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], + + "compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="], + + "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], + + "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "exceljs": ["exceljs@4.4.0", "", { "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", "uuid": "^8.3.0" } }, "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg=="], + + "fast-csv": ["fast-csv@4.3.6", "", { "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" } }, "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + + "listenercount": ["listenercount@1.0.1", "", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="], + + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="], + + "lodash.groupby": ["lodash.groupby@4.6.0", "", {}, "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], + + "lodash.isfunction": ["lodash.isfunction@3.0.9", "", {}, "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="], + + "lodash.isnil": ["lodash.isnil@4.0.0", "", {}, "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isundefined": ["lodash.isundefined@3.0.1", "", {}, "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="], + + "lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + + "rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="], + + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "saxes": ["saxes@5.0.1", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw=="], + + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unzipper": ["unzipper@0.10.14", "", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], + + "@fast-csv/format/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "@fast-csv/parse/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="], + + "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + + "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + } +} diff --git a/generateMonthlySummary.ts b/generateMonthlySummary.ts new file mode 100644 index 0000000..ffc88c3 --- /dev/null +++ b/generateMonthlySummary.ts @@ -0,0 +1,201 @@ +import { join } from "path"; +import { readdirSync, readFileSync, existsSync } from "fs"; +import { spawnSync } from "child_process"; +import dayjs from "dayjs"; +import ExcelJS from "exceljs"; +import fs from "fs-extra"; + +const REPORTS_DIR = join(import.meta.dir, "reports"); +const TEMPLATE_FILE = join(import.meta.dir, "templates", "workstation-summary.typ"); + +interface Assertion { + passed: boolean; + score: number; + minScore: number; + context: any; +} + +interface Report { + username: string; + os: string; + arch: string; + assertions: Record; + stats: { + passed: number; + failed: number; + }; + timestamps: { + start: string; + end: string; + }; +} + +interface EmployeeSummary { + name: string; + latestReport: Report; + timestamp: string; +} + +async function generateMonthlySummary(month: string) { + const monthDir = join(REPORTS_DIR, month); + if (!existsSync(monthDir)) { + console.error(`Month directory not found: ${monthDir}`); + return; + } + + console.log(`Processing reports for month: ${month}...`); + + const employees = readdirSync(monthDir).filter(f => fs.statSync(join(monthDir, f)).isDirectory()); + const summaryData: EmployeeSummary[] = []; + + for (const employeeName of employees) { + const employeeDir = join(monthDir, employeeName); + const files = readdirSync(employeeDir).filter(f => f.endsWith(".report.json")); + + if (files.length === 0) continue; + + // Find latest report by timestamp in filename YYMMDD-HHMMSS + const sortedFiles = files.sort((a, b) => b.localeCompare(a)); + const latestFile = sortedFiles[0]; + const reportPath = join(employeeDir, latestFile); + + try { + const report: Report = JSON.parse(readFileSync(reportPath, "utf-8")); + summaryData.push({ + name: employeeName, + latestReport: report, + timestamp: latestFile.split(".")[0] + }); + } catch (err) { + console.error(`Failed to parse ${reportPath}:`, err); + } + } + + if (summaryData.length === 0) { + console.log("No valid reports found."); + return; + } + + // 1. Generate Excel + const excelPath = join(monthDir, `summary_${month}.xlsx`); + await createExcelReport(summaryData, excelPath); + + // 2. Generate PDF + const pdfPath = join(monthDir, `summary_${month}.pdf`); + await createPdfReport(summaryData, month, pdfPath); +} + +async function createExcelReport(data: EmployeeSummary[], path: string) { + const workbook = new ExcelJS.Workbook(); + const summarySheet = workbook.addWorksheet("Summary"); + const matrixSheet = workbook.addWorksheet("Assertion Matrix"); + + // Summary Sheet + summarySheet.columns = [ + { header: "Employee", key: "name", width: 25 }, + { header: "Last Audit", key: "timestamp", width: 20 }, + { header: "OS", key: "os", width: 10 }, + { header: "Passed", key: "passed", width: 10 }, + { header: "Failed", key: "failed", width: 10 }, + { header: "Score %", key: "scorePct", width: 12 }, + ]; + + data.forEach(s => { + const total = s.latestReport.stats.passed + s.latestReport.stats.failed; + summarySheet.addRow({ + name: s.name, + timestamp: s.timestamp, + os: s.latestReport.os, + passed: s.latestReport.stats.passed, + failed: s.latestReport.stats.failed, + scorePct: ((s.latestReport.stats.passed / total) * 100).toFixed(1) + "%" + }); + }); + + // Matrix Sheet + const allAssertionKeys = Array.from(new Set(data.flatMap(s => Object.keys(s.latestReport.assertions)))).sort(); + matrixSheet.getRow(1).values = ["Employee", ...allAssertionKeys]; + + data.forEach(s => { + const rowValues = [s.name]; + allAssertionKeys.forEach(key => { + const assertion = s.latestReport.assertions[key]; + rowValues.push(assertion ? (assertion.passed ? "PASS" : "FAIL") : "N/A"); + }); + + const row = matrixSheet.addRow(rowValues); + + // Color coding PASS/FAIL + row.eachCell((cell, colNumber) => { + if (colNumber === 1) return; // Skip Employee name + if (cell.value === "PASS") { + cell.font = { color: { argb: "FF006100" }, bold: true }; + cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFC6EFCE' } }; + } else if (cell.value === "FAIL") { + cell.font = { color: { argb: "FF9C0006" }, bold: true }; + cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFC7CE' } }; + } + }); + }); + + // Basic styling + [summarySheet, matrixSheet].forEach(sheet => { + sheet.getRow(1).font = { bold: true }; + sheet.getRow(1).fill = { type: 'pattern', pattern:'solid', fgColor:{argb:'FFE0E0E0'} }; + sheet.getRow(1).alignment = { horizontal: 'center' }; + }); + + await workbook.xlsx.writeFile(path); + console.log(`✅ Excel generated: ${path}`); +} + +async function createPdfReport(data: EmployeeSummary[], month: string, path: string) { + const allAssertionKeys = Array.from(new Set(data.flatMap(s => Object.keys(s.latestReport.assertions)))).sort(); + + const metadata = { + title: "Workstation Compliance Summary", + month: month, + date: dayjs().format("YYYY-MM-DD"), + timestamp: dayjs().format("YYYY-MM-DD HH:mm:ss"), + employeeCount: data.length + }; + + const processedData = { + assertions: allAssertionKeys, + employees: data.map(s => ({ + name: s.name, + passed: s.latestReport.stats.passed, + failed: s.latestReport.stats.failed, + results: allAssertionKeys.map(key => ({ + key, + passed: s.latestReport.assertions[key]?.passed ?? null + })) + })) + }; + + const jsonTmpPath = join(REPORTS_DIR, month, ".summary.json"); + fs.writeJsonSync(jsonTmpPath, processedData); + + const relativeJsonPath = "/" + jsonTmpPath.replace(import.meta.dir + "/", ""); + + const result = spawnSync("typst", [ + "compile", + TEMPLATE_FILE, + path, + "--input", `dataPath=${relativeJsonPath}`, + "--input", `metadata=${JSON.stringify(metadata)}`, + "--root", import.meta.dir + ]); + + if (result.status === 0) { + console.log(`✅ PDF generated: ${path}`); + fs.removeSync(jsonTmpPath); + } else { + console.error(`❌ Failed to generate PDF`); + console.error(result.stderr.toString()); + } +} + +// CLI entry point +const monthArg = process.argv[2] || dayjs().format("YYMM"); +generateMonthlySummary(monthArg).catch(console.error); diff --git a/package.json b/package.json new file mode 100644 index 0000000..ccf1915 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "employee-workstation-report", + "version": "1.0.0", + "description": "ISO Compliance Reporting for Employee Workstations", + "main": "generateMonthlySummary.ts", + "type": "module", + "scripts": { + "start": "bun run generateMonthlySummary.ts", + "dev": "bun --watch run generateMonthlySummary.ts" + }, + "dependencies": { + "dayjs": "^1.11.10", + "exceljs": "^4.4.0", + "fs-extra": "^11.2.0" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "^20.11.0", + "bun-types": "latest", + "typescript": "^5.4.0" + } +} diff --git a/templates/core/design-system.typ b/templates/core/design-system.typ new file mode 100644 index 0000000..fead674 --- /dev/null +++ b/templates/core/design-system.typ @@ -0,0 +1,99 @@ +#let lixus-report( + title: "", + subtitle: "", + date: none, + author: "Lixus DevOps", + logo: "lixusLogo.1012x362.png", + summary: none, + summaryTitle: "Summary", + toc: false, + body +) = { + set page( + paper: "a4", + margin: (x: 2cm, y: 2.5cm), + header: context { + if counter(page).get().first() > 1 { + set text(8pt, fill: gray) + grid( + columns: (1fr, 1fr), + align(left)[#title], + align(right)[#subtitle] + ) + } + }, + footer: [ + #set text(8pt, fill: gray) + #line(length: 100%, stroke: 0.5pt + gray) + #grid( + columns: (1fr, 1fr), + [Generated on #date], + align(right)[Page #context counter(page).display()] + ) + ] + ) + + set text(font: "Inter", size: 11pt, weight: "regular") + + // Title Page + if title != "" { + align(center + horizon)[ + #if logo != none { + image(logo, width: 40%) + v(2cm) + } + #text(28pt, weight: "bold", fill: rgb("#1a237e"))[#title] + #v(0.5cm) + #text(16pt, fill: gray)[#subtitle] + #v(1cm) + #text(12pt, fill: gray)[#date] + #v(2cm) + ] + + // Summary on Title Page + if summary != none { + v(1fr) + align(left)[ + #text(14pt, weight: "bold", fill: rgb("#1a237e"))[#summaryTitle] + #v(0.2cm) + #set text(size: 10pt) + #summary + ] + } + pagebreak() + } + + // Content styling + show heading: set text(fill: rgb("#1a237e")) + show heading.where(level: 1): set text(size: 18pt) + show heading: it => { + it + v(0.3cm) + } + + show outline.entry.where(level: 1): it => { + v(12pt, weak: true) + strong(it) + } + + if toc { + outline(indent: auto) + pagebreak() + } + + body +} + +#let report-table(columns, header, ..rows) = { + table( + columns: columns, + inset: (x: 5pt, y: 7.5pt), + align: horizon, + stroke: (x, y) => if y == 0 { none } else { 0.5pt + gray.lighten(50%) }, + fill: (x, y) => if y == 0 { rgb("#1a237e") } else if calc.even(y) { gray.lighten(95%) } else { none }, + table.header( + ..header.map(it => text(fill: white, weight: "semibold")[#it]) + ), + ..rows + ) +} diff --git a/templates/core/lixusLogo.1012x362.png b/templates/core/lixusLogo.1012x362.png new file mode 100644 index 0000000000000000000000000000000000000000..c796a88f8c65d91b85949f1dfd3e08d70c660ef3 GIT binary patch literal 14130 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 literal 0 HcmV?d00001 diff --git a/templates/workstation-summary.typ b/templates/workstation-summary.typ new file mode 100644 index 0000000..c1f7a01 --- /dev/null +++ b/templates/workstation-summary.typ @@ -0,0 +1,112 @@ +#import "core/design-system.typ": lixus-report, report-table + +#let data = json(sys.inputs.dataPath) +#let metadata = json(bytes(sys.inputs.metadata)) + +#let pass-icon = text(fill: green.darken(20%), weight: "bold")[✔] +#let fail-icon = text(fill: red.darken(20%), weight: "bold")[✘] +#let na-icon = text(fill: gray)[-] + +#show: lixus-report.with( + title: metadata.title, + subtitle: "Monthly Manager Summary - " + metadata.month, + date: metadata.date, +) + += Fleet Overview +This report summarizes the workstation compliance status for *#metadata.employeeCount* employees during the month of *#metadata.month*. + +#grid( + columns: (1fr, 1fr), + gutter: 1cm, + [ + #set align(center) + #rect(stroke: 1pt + gray.lighten(50%), inset: 10pt, radius: 5pt)[ + #text(12pt, weight: "bold")[Total Employees] \ + #text(24pt, weight: "bold", fill: rgb("#1a237e"))[#metadata.employeeCount] + ] + ], + [ + #set align(left) + *Snapshot Metadata:* \ + - Generated: #metadata.timestamp \ + - Compliance Standard: Lixus Workstation v1.0 \ + - Audit Type: Automated Periodic Review + ] +) + +#v(1cm) + += Compliance Matrix +The following table shows the pass/fail status for each security assertion across the fleet. + +#set text(size: 8pt) +#let headers = ("Employee", ..data.assertions.map(a => a.split("_").map(w => w.slice(0,1)).join(""))) + +// Legend for abbreviations +#v(0.5cm) +*Header Legend:* +#grid( + columns: (1fr, 1fr, 1fr, 1fr), + gutter: 5pt, + ..data.assertions.map(a => [ + #text(weight: "bold")[#a.split("_").map(w => w.slice(0,1)).join("")]: #lower(a.replace("_", " ")) + ]) +) +#v(0.5cm) + +#let table-rows = () +#for emp in data.employees { + table-rows.push(strong(emp.name)) + for res in emp.results { + if res.passed == true { + table-rows.push(pass-icon) + } else if res.passed == false { + table-rows.push(fail-icon) + } else { + table-rows.push(na-icon) + } + } +} + +#report-table( + (1.5fr, ..data.assertions.map(a => 1fr)), + headers, + ..table-rows +) + +#v(1cm) + += Detailed Statistics +#let total-tests = data.employees.len() * data.assertions.len() +#let total-passed = 0 +#for emp in data.employees { + total-passed += emp.passed +} +#let total-failed = 0 +#for emp in data.employees { + total-failed += emp.failed +} + +#let health-pct = calc.round((total-passed / (total-passed + total-failed)) * 100) + +#grid( + columns: (1fr, 2fr), + gutter: 1cm, + [ + #text(14pt, weight: "bold")[Overall Fleet Health] \ + #text(32pt, weight: "bold", fill: if health-pct > 90 { green } else if health-pct > 75 { orange } else { red })[#health-pct%] + ], + [ + *Audit Summary:* \ + - Total Assertions Verified: #(total-passed + total-failed) \ + - Total Passes: #total-passed \ + - Total Failures: #total-failed \ + \ + #if total-failed > 0 [ + #text(fill: red)[*Action Required:*] Several workstations have failed critical security assertions. Please refer to the matrix above and the individual report logs for remediation steps. + ] else [ + #text(fill: green.darken(20%))[*Fleet Secure:*] All workstations have passed the current security baseline. + ] + ] +) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0377f71 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "lib": ["ESNext", "DOM"], + "types": ["bun-types"] + } +}