From 9e7e868e7df17b0733320374b73d77577d421a2a Mon Sep 17 00:00:00 2001 From: sneedmaster Date: Mon, 20 Apr 2026 21:23:00 +0200 Subject: [PATCH] =?UTF-8?q?Z=C3=A1znamy=20v=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 260 ++++++++++-------- package.json | 2 +- scripts/create_admin.ts | 2 +- scripts/map_assets.ts | 4 +- src/cache.ts | 2 +- src/db/client.ts | 2 +- src/lib/ip.ts | 2 +- src/lib/log.ts | 106 +++++++ src/lib/logger.ts | 30 -- src/lib/page.ts | 2 +- src/lib/session.ts | 2 +- src/main.ts | 2 +- src/tasks/prune_bans.ts | 2 +- src/tasks/prune_captchas.ts | 2 +- src/tasks/prune_files.ts | 2 +- src/web/routes/actions/post_actions.ts | 2 +- src/web/routes/mod/actions/ban_actions.ts | 2 +- src/web/routes/mod/actions/banner_actions.ts | 2 +- src/web/routes/mod/actions/board_actions.ts | 2 +- src/web/routes/mod/actions/create_banner.ts | 2 +- src/web/routes/mod/actions/create_board.ts | 2 +- src/web/routes/mod/actions/create_filter.ts | 2 +- src/web/routes/mod/actions/create_news.ts | 2 +- .../routes/mod/actions/create_restriction.ts | 2 +- src/web/routes/mod/actions/create_user.ts | 2 +- src/web/routes/mod/actions/filter_actions.ts | 2 +- src/web/routes/mod/actions/news_actions.ts | 2 +- .../routes/mod/actions/restriction_actions.ts | 2 +- .../routes/mod/actions/update_board_config.ts | 2 +- .../routes/mod/actions/update_news_content.ts | 2 +- .../mod/actions/update_user_permissions.ts | 2 +- src/web/routes/mod/actions/user_actions.ts | 2 +- src/web/routes/mod/log.tsx | 101 +++++++ src/web/routes/mod/logs.tsx | 47 +++- src/web/routes/page.tsx | 10 +- src/web/routing/register_mod.ts | 2 + src/web/templates/error_page.tsx | 2 +- web/ts/auto_update.ts | 2 +- 38 files changed, 433 insertions(+), 187 deletions(-) create mode 100644 src/lib/log.ts delete mode 100644 src/lib/logger.ts create mode 100644 src/web/routes/mod/log.tsx diff --git a/package-lock.json b/package-lock.json index a57f887..25ef109 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@kitajs/html": "^4.2.13", "@kitajs/ts-html-plugin": "^4.1.4", - "@logdna/tail-file": "^4.0.2", "@minify-html/node": "^0.18.1", "apache-crypt": "^1.2.6", "async-mutex": "^0.5.0", @@ -30,6 +29,7 @@ "postgres-migrations": "^5.3.0", "prettier": "^3.8.1", "read": "^5.0.1", + "read-last-lines": "^1.8.0", "sass": "^1.98.0", "sharp": "^0.34.5", "superjson": "^2.2.6", @@ -96,16 +96,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/cli/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -162,16 +152,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", @@ -219,16 +199,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", @@ -251,16 +221,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", @@ -279,16 +239,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", @@ -1597,16 +1547,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -2961,15 +2901,6 @@ "typescript": "^5.9.3" } }, - "node_modules/@logdna/tail-file": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@logdna/tail-file/-/tail-file-4.0.2.tgz", - "integrity": "sha512-aEZF3LanJoewsQ9J1CRk5wvOqvQvw4lhxitSMXz3sQPgfezamzwW+pGvKXxvht7EvJEYSlYPxfSjL6T2bDSEkg==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/@minify-html/node": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/@minify-html/node/-/node-0.18.1.tgz", @@ -3898,6 +3829,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/utils": { "version": "8.58.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", @@ -4026,6 +3970,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4119,16 +4069,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", @@ -4164,9 +4104,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", - "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4675,9 +4615,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.339.tgz", - "integrity": "sha512-Is+0BBHJ4NrdpAYiperrmp53pLywG/yV/6lIMTAnhxvzj/Cmn5Q/ogSHC6AKe7X+8kPLxxFk0cs5oc/3j/fxIg==", + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", "dev": true, "license": "ISC" }, @@ -5543,9 +5483,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5889,9 +5829,9 @@ } }, "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5923,6 +5863,18 @@ "npm": ">=6" } }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -6089,16 +6041,6 @@ "node": ">=6" } }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/make-dir/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -6195,6 +6137,17 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6296,6 +6249,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -6579,13 +6541,13 @@ } }, "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, "node_modules/postcss": { @@ -6695,6 +6657,19 @@ "dev": true, "license": "MIT" }, + "node_modules/postcss-cli/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/postcss-cli/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -7101,6 +7076,25 @@ "pify": "^2.3.0" } }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-last-lines": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/read-last-lines/-/read-last-lines-1.8.0.tgz", + "integrity": "sha512-oPL0cnZkhsO2xF7DBrdzVhXSNajPP5TzzCim/2IAjeGb17ArLLTRriI/ceV6Rook3L27mvbrOvLlf9xYYnaftQ==", + "license": "MIT", + "dependencies": { + "mz": "^2.7.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7316,15 +7310,13 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -7422,6 +7414,18 @@ "@img/sharp-win32-x64": "0.34.5" } }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7541,16 +7545,13 @@ } }, "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/source-map-js": { @@ -7680,6 +7681,27 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/timestring": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/timestring/-/timestring-7.0.0.tgz", diff --git a/package.json b/package.json index 002ed6e..198551d 100755 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "dependencies": { "@kitajs/html": "^4.2.13", "@kitajs/ts-html-plugin": "^4.1.4", - "@logdna/tail-file": "^4.0.2", "@minify-html/node": "^0.18.1", "apache-crypt": "^1.2.6", "async-mutex": "^0.5.0", @@ -75,6 +74,7 @@ "postgres-migrations": "^5.3.0", "prettier": "^3.8.1", "read": "^5.0.1", + "read-last-lines": "^1.8.0", "sass": "^1.98.0", "sharp": "^0.34.5", "superjson": "^2.2.6", diff --git a/scripts/create_admin.ts b/scripts/create_admin.ts index edf0b22..af091b1 100755 --- a/scripts/create_admin.ts +++ b/scripts/create_admin.ts @@ -1,7 +1,7 @@ import { loadConfig } from "../src/config.js"; import { initDb, postgres } from "../src/db/client.js"; import { createUser } from "../src/db/user.js"; -import { czchanLog } from "../src/lib/logger.js"; +import { czchanLog } from "../src/lib/log.js"; import bcrypt from "bcrypt"; import { read } from "read"; diff --git a/scripts/map_assets.ts b/scripts/map_assets.ts index 607d8e5..da0264e 100644 --- a/scripts/map_assets.ts +++ b/scripts/map_assets.ts @@ -1,4 +1,4 @@ -import { czchanLog } from "../src/lib/logger.js"; +import { czchanLog } from "../src/lib/log.js"; import { readdir, mkdir, writeFile } from "fs/promises"; import { parse } from "path"; @@ -16,7 +16,7 @@ await (async () => { await czchanLog( "build", "info", - `Kód ${statusCode}: nalezeno ${images.length} obrázků `, + `Nalezeny obrázky pro kód ${statusCode}: celkem ${images.length} `, ); errorImages[statusCode] = images.sort(); diff --git a/src/cache.ts b/src/cache.ts index af8a722..c157c50 100755 --- a/src/cache.ts +++ b/src/cache.ts @@ -9,7 +9,7 @@ import { cachePost } from "./db/cache/post.js"; import { cacheRestriction } from "./db/cache/restriction.js"; import { cacheUser } from "./db/cache/user.js"; import { postgres } from "./db/client.js"; -import { czchanLog } from "./lib/logger.js"; +import { czchanLog } from "./lib/log.js"; import type { User, Banner, diff --git a/src/db/client.ts b/src/db/client.ts index 873d2e0..e6cc0e1 100755 --- a/src/db/client.ts +++ b/src/db/client.ts @@ -1,5 +1,5 @@ import type { CzchanConfig } from "../config.js"; -import { czchanLog } from "../lib/logger.js"; +import { czchanLog } from "../lib/log.js"; import { Pool } from "pg"; import { migrate } from "postgres-migrations"; diff --git a/src/lib/ip.ts b/src/lib/ip.ts index d14281d..c94b63f 100644 --- a/src/lib/ip.ts +++ b/src/lib/ip.ts @@ -1,6 +1,6 @@ import { readAllBans } from "../db/ban.js"; import { createCIDRIndex, createIndexedCIDR } from "./cidr.js"; -import { czchanLog } from "./logger.js"; +import { czchanLog } from "./log.js"; import { randomUUID } from "crypto"; import { readdir, readFile } from "fs/promises"; import ipaddr from "ipaddr.js"; diff --git a/src/lib/log.ts b/src/lib/log.ts new file mode 100644 index 0000000..4ad3658 --- /dev/null +++ b/src/lib/log.ts @@ -0,0 +1,106 @@ +import { CzchanError } from "./error.js"; +import { appendFile, readdir, stat } from "node:fs/promises"; +import { join, parse } from "node:path"; +import readLastLines from "read-last-lines"; + +type Log = { + name: string; + created: Date; + modified: Date; + size: number; +}; + +type LogLine = { + timestamp: Date; + log: string; + level: "info" | "warn" | "error"; + message: string; + actor: string; +}; + +const czchanLog = async ( + log: string, + level: "info" | "warn" | "error", + message: string, + actor?: string, +) => { + const now = new Date(); + const timestamp = now.getTime(); + + const consoleLogLine = `[${now.toISOString()}] [${level}] [${log}] (${actor ?? "system"}) ${message}`; + const fileLogLine = `${JSON.stringify({ + timestamp, + log, + level, + message, + actor: actor ?? "system", + })}\n`; + + switch (level) { + case "info": + console.info(consoleLogLine); + break; + case "warn": + console.warn(consoleLogLine); + break; + case "error": + console.error(consoleLogLine); + break; + } + + await appendFile(join("logs", `${log}.jsonl`), fileLogLine); + await appendFile(join("logs", `all.jsonl`), fileLogLine); +}; + +const readLogs = async () => { + const result = []; + const logs = (await readdir("logs")).map((log) => parse(log).name).sort(); + + for (const log of logs) { + const stats = await stat(join("logs", `${log}.jsonl`)); + const name = log; + const created = stats.birthtime; + const modified = stats.mtime; + const size = stats.size; + + result.push({ name, created, modified, size }); + } + + return result; +}; + +const readLogPage = async (log: string, page: number): Promise => { + const limit = 100; + const offset = page * limit; + + let lines: string; + + try { + lines = await readLastLines.read( + join("logs", `${log}.jsonl`), + offset + limit, + ); + } catch (_) { + throw new CzchanError(`Záznam ${log} neexistuje.`, 404); + } + + const splitLines = lines.split(/\r\n|\r|\n/g).filter(Boolean); + const end = Math.min(splitLines.length, splitLines.length - offset + limit); + + if (end <= 0) { + return []; + } + + return splitLines + .slice(-end) + .slice(-limit) + .map((line) => { + const entry = JSON.parse(line); + entry.timestamp = new Date(entry.timestamp); + return entry as LogLine; + }) + .reverse(); +}; + +export type { Log, LogLine }; +export { czchanLog, readLogs, readLogPage }; diff --git a/src/lib/logger.ts b/src/lib/logger.ts deleted file mode 100644 index b9d4e89..0000000 --- a/src/lib/logger.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { appendFile } from "node:fs/promises"; -import { join } from "node:path"; - -const czchanLog = async ( - log: string, - level: "info" | "warn" | "error", - message: string, - actor?: string, -) => { - const now = new Date(); - const consoleLogLine = `[${now.toISOString()}] [${level}] [${log}] (${actor ?? "system"}) ${message}`; - const fileLogLine = `${now.getTime()},${log},${level},${message},${actor ?? "system"}\n`; - - switch (level) { - case "info": - console.info(consoleLogLine); - break; - case "warn": - console.warn(consoleLogLine); - break; - case "error": - console.error(consoleLogLine); - break; - } - - await appendFile(join("logs", `${log}.csv`), fileLogLine); - await appendFile(join("logs", `all.csv`), fileLogLine); -}; - -export { czchanLog }; diff --git a/src/lib/page.ts b/src/lib/page.ts index e1bab57..74f120a 100644 --- a/src/lib/page.ts +++ b/src/lib/page.ts @@ -1,5 +1,5 @@ import { redis } from "../cache.js"; -import { czchanLog } from "./logger.js"; +import { czchanLog } from "./log.js"; import minifyHTML from "@minify-html/node"; import { readdir, readFile } from "node:fs/promises"; import { join, parse } from "node:path"; diff --git a/src/lib/session.ts b/src/lib/session.ts index faca004..bbb2e1b 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,7 +1,7 @@ import { redis } from "../cache.js"; import { createBan, readBans } from "../db/ban.js"; import { rectifyCIDR } from "./ip.js"; -import { czchanLog } from "./logger.js"; +import { czchanLog } from "./log.js"; import { randomUUID } from "crypto"; import ipaddr from "ipaddr.js"; import SuperJSON from "superjson"; diff --git a/src/main.ts b/src/main.ts index abe7eab..fabbfc8 100755 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { loadConfig } from "./config.js"; import { initDb } from "./db/client.js"; import { CzchanError } from "./lib/error.js"; import { initIPLists } from "./lib/ip.js"; -import { czchanLog } from "./lib/logger.js"; +import { czchanLog } from "./lib/log.js"; import { initPages } from "./lib/page.js"; import { pruneBans } from "./tasks/prune_bans.js"; import { pruneCaptchas } from "./tasks/prune_captchas.js"; diff --git a/src/tasks/prune_bans.ts b/src/tasks/prune_bans.ts index 18ff65b..db18117 100644 --- a/src/tasks/prune_bans.ts +++ b/src/tasks/prune_bans.ts @@ -1,6 +1,6 @@ import { deleteBan } from "../db/ban.js"; import { postgres } from "../db/client.js"; -import { czchanLog } from "../lib/logger.js"; +import { czchanLog } from "../lib/log.js"; import type { Ban } from "../schema/tables.js"; const pruneBans = async () => { diff --git a/src/tasks/prune_captchas.ts b/src/tasks/prune_captchas.ts index 20ab58a..f8be4d2 100644 --- a/src/tasks/prune_captchas.ts +++ b/src/tasks/prune_captchas.ts @@ -1,5 +1,5 @@ import { redis } from "../cache.js"; -import { czchanLog } from "../lib/logger.js"; +import { czchanLog } from "../lib/log.js"; import { readdir, unlink } from "fs/promises"; import { join, parse } from "path"; diff --git a/src/tasks/prune_files.ts b/src/tasks/prune_files.ts index 0f3a55b..3f8fc0c 100644 --- a/src/tasks/prune_files.ts +++ b/src/tasks/prune_files.ts @@ -5,7 +5,7 @@ import { removeUsageFromArray, } from "../db/file_record.js"; import { readPosts } from "../db/post.js"; -import { czchanLog } from "../lib/logger.js"; +import { czchanLog } from "../lib/log.js"; import { bytesToSize } from "../lib/util.js"; import type { Banner, Post } from "../schema/tables.js"; import { unlink } from "fs/promises"; diff --git a/src/web/routes/actions/post_actions.ts b/src/web/routes/actions/post_actions.ts index 928fec4..cdffcda 100644 --- a/src/web/routes/actions/post_actions.ts +++ b/src/web/routes/actions/post_actions.ts @@ -20,7 +20,7 @@ import { updatePostLooped, } from "../../../db/post.js"; import { CzchanError } from "../../../lib/error.js"; -import { czchanLog } from "../../../lib/logger.js"; +import { czchanLog } from "../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../lib/permissions.js"; import type { Board, Post } from "../../../schema/tables.js"; import { ZPostActionsForm } from "../../../schema/validation/post.js"; diff --git a/src/web/routes/mod/actions/ban_actions.ts b/src/web/routes/mod/actions/ban_actions.ts index aae7ec3..d44220c 100644 --- a/src/web/routes/mod/actions/ban_actions.ts +++ b/src/web/routes/mod/actions/ban_actions.ts @@ -5,7 +5,7 @@ import { updateBanAppealResponse, } from "../../../../db/ban.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { Ban } from "../../../../schema/tables.js"; import { BanActionsForm } from "../../../../schema/validation/ban.js"; diff --git a/src/web/routes/mod/actions/banner_actions.ts b/src/web/routes/mod/actions/banner_actions.ts index 474c9ad..a71c919 100644 --- a/src/web/routes/mod/actions/banner_actions.ts +++ b/src/web/routes/mod/actions/banner_actions.ts @@ -1,6 +1,6 @@ import { deleteBanner, readBanners } from "../../../../db/banner.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { Banner } from "../../../../schema/tables.js"; import { BannerActionsForm } from "../../../../schema/validation/banner.js"; diff --git a/src/web/routes/mod/actions/board_actions.ts b/src/web/routes/mod/actions/board_actions.ts index 7846371..eab933b 100644 --- a/src/web/routes/mod/actions/board_actions.ts +++ b/src/web/routes/mod/actions/board_actions.ts @@ -5,7 +5,7 @@ import { updateBoardName, } from "../../../../db/board.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { Board } from "../../../../schema/tables.js"; import { diff --git a/src/web/routes/mod/actions/create_banner.ts b/src/web/routes/mod/actions/create_banner.ts index 76d1e7c..b2722a4 100644 --- a/src/web/routes/mod/actions/create_banner.ts +++ b/src/web/routes/mod/actions/create_banner.ts @@ -2,7 +2,7 @@ import { createBanner } from "../../../../db/banner.js"; import { addUsageToArray, readFileRecord } from "../../../../db/file_record.js"; import { CzchanError } from "../../../../lib/error.js"; import { processFiles } from "../../../../lib/file.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { FileRecord } from "../../../../schema/tables.js"; import type { Request, Response } from "express"; diff --git a/src/web/routes/mod/actions/create_board.ts b/src/web/routes/mod/actions/create_board.ts index 0b0b83d..3466671 100644 --- a/src/web/routes/mod/actions/create_board.ts +++ b/src/web/routes/mod/actions/create_board.ts @@ -1,6 +1,6 @@ import { createBoard } from "../../../../db/board.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import { CreateBoardForm } from "../../../../schema/validation/board.js"; import type { Request, Response } from "express"; diff --git a/src/web/routes/mod/actions/create_filter.ts b/src/web/routes/mod/actions/create_filter.ts index b880451..23ce762 100644 --- a/src/web/routes/mod/actions/create_filter.ts +++ b/src/web/routes/mod/actions/create_filter.ts @@ -1,7 +1,7 @@ import { readBoard } from "../../../../db/board.js"; import { createFilter } from "../../../../db/filter.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import { CreateFilterForm } from "../../../../schema/validation/filter.js"; import type { Request, Response } from "express"; diff --git a/src/web/routes/mod/actions/create_news.ts b/src/web/routes/mod/actions/create_news.ts index 46da9f9..2cb9a8e 100644 --- a/src/web/routes/mod/actions/create_news.ts +++ b/src/web/routes/mod/actions/create_news.ts @@ -1,7 +1,7 @@ import { createNews } from "../../../../db/news.js"; import { CzchanError } from "../../../../lib/error.js"; import { formatContent } from "../../../../lib/formatting.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import { CreateNewsForm } from "../../../../schema/validation/news.js"; import type { Request, Response } from "express"; diff --git a/src/web/routes/mod/actions/create_restriction.ts b/src/web/routes/mod/actions/create_restriction.ts index 578cec5..7ea7ce9 100644 --- a/src/web/routes/mod/actions/create_restriction.ts +++ b/src/web/routes/mod/actions/create_restriction.ts @@ -1,7 +1,7 @@ import { readBoard } from "../../../../db/board.js"; import { createRestriction } from "../../../../db/restriction.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import { CreateRestrictionForm } from "../../../../schema/validation/restriction.js"; import type { Request, Response } from "express"; diff --git a/src/web/routes/mod/actions/create_user.ts b/src/web/routes/mod/actions/create_user.ts index 1459165..d2acafd 100644 --- a/src/web/routes/mod/actions/create_user.ts +++ b/src/web/routes/mod/actions/create_user.ts @@ -1,6 +1,6 @@ import { createUser } from "../../../../db/user.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, canUserApplyRole, diff --git a/src/web/routes/mod/actions/filter_actions.ts b/src/web/routes/mod/actions/filter_actions.ts index d994340..68a61c9 100644 --- a/src/web/routes/mod/actions/filter_actions.ts +++ b/src/web/routes/mod/actions/filter_actions.ts @@ -4,7 +4,7 @@ import { updateFilterPriority, } from "../../../../db/filter.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { Filter } from "../../../../schema/tables.js"; import { diff --git a/src/web/routes/mod/actions/news_actions.ts b/src/web/routes/mod/actions/news_actions.ts index 8fcd4a3..6e35aac 100644 --- a/src/web/routes/mod/actions/news_actions.ts +++ b/src/web/routes/mod/actions/news_actions.ts @@ -1,6 +1,6 @@ import { deleteNews, readNews } from "../../../../db/news.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { News } from "../../../../schema/tables.js"; import { NewsActionsForm } from "../../../../schema/validation/news.js"; diff --git a/src/web/routes/mod/actions/restriction_actions.ts b/src/web/routes/mod/actions/restriction_actions.ts index 3598629..c6292b2 100644 --- a/src/web/routes/mod/actions/restriction_actions.ts +++ b/src/web/routes/mod/actions/restriction_actions.ts @@ -3,7 +3,7 @@ import { readRestrictions, } from "../../../../db/restriction.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import type { Restriction } from "../../../../schema/tables.js"; import { RestrictionActionsForm } from "../../../../schema/validation/restriction.js"; diff --git a/src/web/routes/mod/actions/update_board_config.ts b/src/web/routes/mod/actions/update_board_config.ts index c537213..bb01d70 100644 --- a/src/web/routes/mod/actions/update_board_config.ts +++ b/src/web/routes/mod/actions/update_board_config.ts @@ -1,6 +1,6 @@ import { readBoard, updateBoardConfig } from "../../../../db/board.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import { PrimaryBoardConfigForm, diff --git a/src/web/routes/mod/actions/update_news_content.ts b/src/web/routes/mod/actions/update_news_content.ts index b4ab087..fc71c94 100644 --- a/src/web/routes/mod/actions/update_news_content.ts +++ b/src/web/routes/mod/actions/update_news_content.ts @@ -1,7 +1,7 @@ import { readNewsPost, updateNews } from "../../../../db/news.js"; import { CzchanError } from "../../../../lib/error.js"; import { formatContent } from "../../../../lib/formatting.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../../lib/permissions.js"; import { UpdateNewsContentForm } from "../../../../schema/validation/news.js"; import type { Request, Response } from "express"; diff --git a/src/web/routes/mod/actions/update_user_permissions.ts b/src/web/routes/mod/actions/update_user_permissions.ts index b1e1478..a00bc6e 100644 --- a/src/web/routes/mod/actions/update_user_permissions.ts +++ b/src/web/routes/mod/actions/update_user_permissions.ts @@ -1,6 +1,6 @@ import { readUser, updateUserPermissions } from "../../../../db/user.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, canUserActOn, diff --git a/src/web/routes/mod/actions/user_actions.ts b/src/web/routes/mod/actions/user_actions.ts index 7da6803..caa9182 100644 --- a/src/web/routes/mod/actions/user_actions.ts +++ b/src/web/routes/mod/actions/user_actions.ts @@ -6,7 +6,7 @@ import { updateUserRank, } from "../../../../db/user.js"; import { CzchanError } from "../../../../lib/error.js"; -import { czchanLog } from "../../../../lib/logger.js"; +import { czchanLog } from "../../../../lib/log.js"; import { canUser, canUserActOn, diff --git a/src/web/routes/mod/log.tsx b/src/web/routes/mod/log.tsx new file mode 100644 index 0000000..aafab83 --- /dev/null +++ b/src/web/routes/mod/log.tsx @@ -0,0 +1,101 @@ +import { CzchanError } from "../../../lib/error.js"; +import { type LogLine, readLogPage } from "../../../lib/log.js"; +import { canUser, CzchanPerm } from "../../../lib/permissions.js"; +import { zPage } from "../../../schema/validation/common.js"; +import Page from "../../components/chrome/page.js"; +import PageBody from "../../components/chrome/page_body.js"; +import PageHead from "../../components/chrome/page_head.js"; +import Datetime from "../../components/primitives/datetime.js"; +import { getCtx, type TemplateCtx } from "../../ctx.js"; +import logs from "./logs.js"; +import { type PropsWithChildren } from "@kitajs/html"; +import type { Request, Response } from "express"; + +export default async (req: Request, res: Response) => { + const ctx = await getCtx(req); + + if (!canUser(ctx.user, CzchanPerm.VIEW_LOGS)) { + throw new CzchanError("K tomuto nemáš oprávnění.", 403); + } + + const log = req.params.log.toString(); + const page = zPage.parse(req.query.page ?? "1"); + const lines = await readLogPage(log, page); + const html = Template(ctx, page, log, lines); + + res.send(html); +}; + +const Template = ( + ctx: TemplateCtx, + page: number, + log: string, + lines: LogLine[], +) => ( + + + +

+ Záznam ({log}) +

+
+ +
+
+ + + + + + + + + + + + {lines.map((line) => ( + + + + + + + + ))} + {logs.length === 0 && ( + + + + )} + +
DatumZáznamÚroveňZprávaČinitel
+ + {line.log}{line.level}{line.message}{line.actor}
+ Nic tady není... +
+
+
+ +
+
+
+); + +const LogNav = ({ + log, + page, +}: PropsWithChildren<{ log: string; page: number }>) => ( + +); diff --git a/src/web/routes/mod/logs.tsx b/src/web/routes/mod/logs.tsx index 6076ea9..caa987c 100755 --- a/src/web/routes/mod/logs.tsx +++ b/src/web/routes/mod/logs.tsx @@ -1,9 +1,12 @@ import { CzchanError } from "../../../lib/error.js"; +import { type Log, readLogs } from "../../../lib/log.js"; import { canUser, CzchanPerm } from "../../../lib/permissions.js"; +import { bytesToSize } from "../../../lib/util.js"; import Page from "../../components/chrome/page.js"; import PageBody from "../../components/chrome/page_body.js"; import PageHead from "../../components/chrome/page_head.js"; import ModNav from "../../components/navigation/mod_nav.js"; +import Datetime from "../../components/primitives/datetime.js"; import { getCtx, type TemplateCtx } from "../../ctx.js"; import type { Request, Response } from "express"; @@ -14,20 +17,58 @@ export default async (req: Request, res: Response) => { throw new CzchanError("K tomuto nemáš oprávnění.", 403); } - const html = Template(ctx); + const logs = await readLogs(); + const html = Template(ctx, logs); res.send(html); }; -const Template = (ctx: TemplateCtx) => ( +const Template = (ctx: TemplateCtx, logs: Log[]) => (

Záznamy

-

[TBI]



+
+ + + + + + + + + + + {logs.map((log) => ( + + + + + + + ))} + {logs.length === 0 && ( + + + + )} + +
ZáznamVytvořenUpravenVelikost
+ + {log.name} + + + + + + {bytesToSize(log.size)}
+ Nic tady není... +
+
+
); diff --git a/src/web/routes/page.tsx b/src/web/routes/page.tsx index 4ca5e90..58da0c8 100644 --- a/src/web/routes/page.tsx +++ b/src/web/routes/page.tsx @@ -15,14 +15,18 @@ export default async (req: Request, res: Response) => { throw new CzchanError(`Stránka ${pageName} neexistuje`, 404); } - const html = Template(ctx, pageHtml); + const html = Template(ctx, pageName, pageHtml); res.send(html); }; -const Template = (ctx: TemplateCtx, pageHtml: string) => ( +const Template = (ctx: TemplateCtx, pageName: string, pageHtml: string) => ( - + {pageHtml as "safe"} ); diff --git a/src/web/routing/register_mod.ts b/src/web/routing/register_mod.ts index b6eee25..af135d0 100644 --- a/src/web/routing/register_mod.ts +++ b/src/web/routing/register_mod.ts @@ -24,6 +24,7 @@ import modEditUserPermissions from "../routes/mod/edit/user_permissions.js"; import modFilters from "../routes/mod/filters.js"; import modIndex from "../routes/mod/index.js"; import modIP from "../routes/mod/ip.js"; +import modLog from "../routes/mod/log.js"; import modLogs from "../routes/mod/logs.js"; import modNews from "../routes/mod/news.js"; import modPostInfo from "../routes/mod/post_info.js"; @@ -45,6 +46,7 @@ const registerModRoutes = (router: Router) => { router.get("/mod/restrictions", e(modRestrictions)); router.get("/mod/filters", e(modFilters)); router.get("/mod/logs", e(modLogs)); + router.get("/mod/log/:log", e(modLog)); // Mod info router.get("/mod/ip/:ip", e(modIP)); diff --git a/src/web/templates/error_page.tsx b/src/web/templates/error_page.tsx index d2a7367..7318d9f 100755 --- a/src/web/templates/error_page.tsx +++ b/src/web/templates/error_page.tsx @@ -1,5 +1,5 @@ import { CzchanError } from "../../lib/error.js"; -import { czchanLog } from "../../lib/logger.js"; +import { czchanLog } from "../../lib/log.js"; import Page from "../components/chrome/page.js"; import PageBody from "../components/chrome/page_body.js"; import PageHead from "../components/chrome/page_head.js"; diff --git a/web/ts/auto_update.ts b/web/ts/auto_update.ts index 846dfaf..410aec9 100644 --- a/web/ts/auto_update.ts +++ b/web/ts/auto_update.ts @@ -20,7 +20,7 @@ const autoUpdate = () => { const infoContainer = $("#update-info-container"); infoContainer.append( - '
[Aktualizovat]
', + '
', ); let seconds = 0;