feat:share stats (remaining space/total space) and enforced checks (#68)

* feat(admin): disk usage in admin share management

* build(eslint): update eslint configs

* fix(lang): added missing localisation for disk usage
This commit is contained in:
smp46
2026-05-08 09:14:30 +01:00
committed by GitHub
parent 8d232bdb1e
commit 2af606137d
19 changed files with 365 additions and 145 deletions
-6
View File
@@ -1,6 +0,0 @@
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"root": true
}
+29
View File
@@ -0,0 +1,29 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import prettierConfig from "eslint-config-prettier";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
{
languageOptions: {
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname,
},
},
rules: {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
},
},
{
ignores: ["dist/**", "node_modules/**", "prisma/migrations/**", "eslint.config.mjs"],
}
);
+136 -135
View File
@@ -27,6 +27,7 @@
"body-parser": "^2.2.0",
"cache-manager": "^6.4.2",
"cacheable": "^1.9.0",
"check-disk-space": "^3.4.0",
"clamscan": "^2.4.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
@@ -53,6 +54,8 @@
"yaml": "^2.7.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^9.39.4",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.5",
"@nestjs/testing": "^11.0.17",
@@ -80,6 +83,7 @@
"ts-loader": "^9.5.2",
"tsconfig-paths": "4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.59.2",
"wait-on": "^8.0.3"
}
},
@@ -1162,7 +1166,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
@@ -1178,7 +1181,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
@@ -1194,7 +1196,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
@@ -1210,7 +1211,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
@@ -1226,7 +1226,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@@ -1242,7 +1241,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@@ -1258,7 +1256,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1274,7 +1271,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1290,7 +1286,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1306,7 +1301,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1322,7 +1316,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1338,7 +1331,6 @@
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1354,7 +1346,6 @@
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1370,7 +1361,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1386,7 +1376,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1402,7 +1391,6 @@
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1418,7 +1406,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1434,7 +1421,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
@@ -1450,7 +1436,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
@@ -1466,7 +1451,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
@@ -1482,7 +1466,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
@@ -1498,7 +1481,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
@@ -1514,7 +1496,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@@ -1530,7 +1511,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
@@ -1546,7 +1526,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@@ -1650,9 +1629,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz",
"integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==",
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
"integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1663,7 +1642,7 @@
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.1",
"minimatch": "^3.1.3",
"minimatch": "^3.1.5",
"strip-json-comments": "^3.1.1"
},
"engines": {
@@ -1721,9 +1700,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.39.3",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz",
"integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==",
"version": "9.39.4",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
"integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4470,20 +4449,20 @@
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
"integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz",
"integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/type-utils": "8.56.1",
"@typescript-eslint/utils": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1",
"@typescript-eslint/scope-manager": "8.59.2",
"@typescript-eslint/type-utils": "8.59.2",
"@typescript-eslint/utils": "8.59.2",
"@typescript-eslint/visitor-keys": "8.59.2",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4493,9 +4472,9 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.56.1",
"@typescript-eslint/parser": "^8.59.2",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
@@ -4509,16 +4488,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz",
"integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1",
"@typescript-eslint/scope-manager": "8.59.2",
"@typescript-eslint/types": "8.59.2",
"@typescript-eslint/typescript-estree": "8.59.2",
"@typescript-eslint/visitor-keys": "8.59.2",
"debug": "^4.4.3"
},
"engines": {
@@ -4530,7 +4509,7 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/parser/node_modules/debug": {
@@ -4552,14 +4531,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
"integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz",
"integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.56.1",
"@typescript-eslint/types": "^8.56.1",
"@typescript-eslint/tsconfig-utils": "^8.59.2",
"@typescript-eslint/types": "^8.59.2",
"debug": "^4.4.3"
},
"engines": {
@@ -4570,7 +4549,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service/node_modules/debug": {
@@ -4592,14 +4571,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
"integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz",
"integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1"
"@typescript-eslint/types": "8.59.2",
"@typescript-eslint/visitor-keys": "8.59.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4610,9 +4589,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
"integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz",
"integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4623,21 +4602,21 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
"integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz",
"integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1",
"@typescript-eslint/utils": "8.56.1",
"@typescript-eslint/types": "8.59.2",
"@typescript-eslint/typescript-estree": "8.59.2",
"@typescript-eslint/utils": "8.59.2",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4648,7 +4627,7 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/debug": {
@@ -4670,9 +4649,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
"integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4684,21 +4663,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
"integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz",
"integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.56.1",
"@typescript-eslint/tsconfig-utils": "8.56.1",
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/visitor-keys": "8.56.1",
"@typescript-eslint/project-service": "8.59.2",
"@typescript-eslint/tsconfig-utils": "8.59.2",
"@typescript-eslint/types": "8.59.2",
"@typescript-eslint/visitor-keys": "8.59.2",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.4.0"
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4708,7 +4687,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
@@ -4753,13 +4732,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
@@ -4768,30 +4747,17 @@
"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.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
"integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz",
"integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/typescript-estree": "8.56.1"
"@typescript-eslint/scope-manager": "8.59.2",
"@typescript-eslint/types": "8.59.2",
"@typescript-eslint/typescript-estree": "8.59.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4802,17 +4768,17 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
"integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz",
"integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.56.1",
"@typescript-eslint/types": "8.59.2",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -5630,6 +5596,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/check-disk-space": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz",
"integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -6487,6 +6462,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@eslint/js": {
"version": "9.39.3",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz",
"integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
@@ -7138,7 +7126,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -8986,9 +8973,10 @@
"dev": true
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"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"
},
@@ -9111,17 +9099,6 @@
"@img/sharp-win32-x64": "0.34.1"
}
},
"node_modules/sharp/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"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",
@@ -9574,14 +9551,14 @@
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -9640,9 +9617,9 @@
}
},
"node_modules/ts-api-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
"integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9813,6 +9790,30 @@
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.59.2",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz",
"integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.59.2",
"@typescript-eslint/parser": "8.59.2",
"@typescript-eslint/typescript-estree": "8.59.2",
"@typescript-eslint/utils": "8.59.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/uid": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz",
+4
View File
@@ -36,6 +36,7 @@
"body-parser": "^2.2.0",
"cache-manager": "^6.4.2",
"cacheable": "^1.9.0",
"check-disk-space": "^3.4.0",
"clamscan": "^2.4.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
@@ -62,6 +63,8 @@
"yaml": "^2.7.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^9.39.4",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.5",
"@nestjs/testing": "^11.0.17",
@@ -89,6 +92,7 @@
"ts-loader": "^9.5.2",
"tsconfig-paths": "4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.59.2",
"wait-on": "^8.0.3"
}
}
+2
View File
@@ -17,6 +17,7 @@ import { PrismaModule } from "./prisma/prisma.module";
import { ReverseShareModule } from "./reverseShare/reverseShare.module";
import { ShareModule } from "./share/share.module";
import { UserModule } from "./user/user.module";
import { SystemModule } from "./system/system.module";
@Module({
imports: [
@@ -28,6 +29,7 @@ import { UserModule } from "./user/user.module";
PrismaModule,
JobsModule,
UserModule,
SystemModule,
ThrottlerModule.forRoot([
{
ttl: 60,
+5
View File
@@ -1,6 +1,7 @@
import { Type } from "class-transformer";
import {
IsEmail,
IsNumber,
IsOptional,
IsString,
Length,
@@ -35,4 +36,8 @@ export class CreateShareDTO {
@ValidateNested()
@Type(() => ShareSecurityDTO)
security: ShareSecurityDTO;
@IsNumber()
@IsOptional()
size: number;
}
+2
View File
@@ -4,6 +4,7 @@ import { ClamScanModule } from "src/clamscan/clamscan.module";
import { EmailModule } from "src/email/email.module";
import { FileModule } from "src/file/file.module";
import { ReverseShareModule } from "src/reverseShare/reverseShare.module";
import { SystemModule } from "src/system/system.module";
import { ShareController } from "./share.controller";
import { ShareService } from "./share.service";
@@ -14,6 +15,7 @@ import { ShareService } from "./share.service";
forwardRef(() => ClamScanModule),
ReverseShareModule,
forwardRef(() => FileModule),
SystemModule,
],
controllers: [ShareController],
providers: [ShareService],
+18 -1
View File
@@ -16,6 +16,7 @@ import { EmailService } from "src/email/email.service";
import { FileService } from "src/file/file.service";
import { PrismaService } from "src/prisma/prisma.service";
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
import { SystemService } from "src/system/system.service";
import { parseRelativeDateToAbsolute } from "src/utils/date.util";
import { SHARE_DIRECTORY } from "../constants";
import { CreateShareDTO } from "./dto/createShare.dto";
@@ -31,9 +32,17 @@ export class ShareService {
private jwtService: JwtService,
private reverseShareService: ReverseShareService,
private clamScanService: ClamScanService,
private systemService: SystemService,
) {}
async create(share: CreateShareDTO, user?: User, reverseShareToken?: string) {
if (share.size) {
const systemInfo = await this.systemService.getSystemInfo();
if (systemInfo && systemInfo.total - systemInfo.used < share.size) {
throw new BadRequestException("Not enough disk space available");
}
}
if (!(await this.isShareIdAvailable(share.id)).isAvailable)
throw new BadRequestException("Share id already in use");
@@ -75,9 +84,17 @@ export class ShareService {
recursive: true,
});
const {
size: _size,
security: _security,
recipients: _recipients,
expiration: _expiration,
...shareData
} = share;
const shareTuple = await this.prisma.share.create({
data: {
...share,
...shareData,
expiration: expirationDate,
creator: { connect: user ? { id: user.id } : undefined },
security: { create: share.security },
+4
View File
@@ -0,0 +1,4 @@
export class SystemInfoDTO {
used: number;
total: number;
}
+16
View File
@@ -0,0 +1,16 @@
import { Controller, Get, UseGuards } from "@nestjs/common";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { SystemService } from "./system.service";
import { SystemInfoDTO } from "./dto/systemInfo.dto";
@Controller("system")
export class SystemController {
constructor(private systemService: SystemService) {}
@Get("info")
@UseGuards(JwtGuard, AdministratorGuard)
async getSystemInfo(): Promise<SystemInfoDTO | null> {
return await this.systemService.getSystemInfo();
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { ConfigModule } from "src/config/config.module";
import { SystemController } from "./system.controller";
import { SystemService } from "./system.service";
@Module({
imports: [ConfigModule],
controllers: [SystemController],
providers: [SystemService],
exports: [SystemService],
})
export class SystemModule {}
+45
View File
@@ -0,0 +1,45 @@
import { Injectable, Logger } from "@nestjs/common";
import checkDiskSpace from "check-disk-space";
import { DATA_DIRECTORY } from "src/constants";
import { SystemInfoDTO } from "./dto/systemInfo.dto";
import { ConfigService } from "src/config/config.service";
import * as path from "path";
@Injectable()
export class SystemService {
private readonly logger = new Logger(SystemService.name);
constructor(private configService: ConfigService) {}
async getSystemInfo(): Promise<SystemInfoDTO | null> {
if (this.configService.get("s3.enabled")) {
return null;
}
const resolvedPath = path.resolve(DATA_DIRECTORY);
try {
const diskSpace = await checkDiskSpace(resolvedPath);
return {
used: diskSpace.size - diskSpace.free,
total: diskSpace.size,
};
} catch (e) {
this.logger.warn(
`Failed to check disk space for ${resolvedPath}, falling back to root: ${e.message}`,
);
try {
const diskSpace = await checkDiskSpace("/");
return {
used: diskSpace.size - diskSpace.free,
total: diskSpace.size,
};
} catch (err) {
this.logger.error(
`Failed to check disk space even for root: ${err.message}`,
);
return null;
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
export default [
{
ignores: [
"backend/**",
"frontend/**",
"docs/**",
"scripts/**",
"**/node_modules/**",
"**/dist/**",
"**/.next/**",
],
},
{
files: ["**/*.js", "**/*.mjs"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
"no-unused-vars": "warn",
"no-undef": "error",
},
},
];
@@ -0,0 +1,45 @@
import { Group, Progress, Stack, Text } from "@mantine/core";
import { useEffect, useState } from "react";
import systemService, { SystemInfo } from "../../../services/system.service";
import { byteToHumanSizeString } from "../../../utils/fileSize.util";
import { FormattedMessage } from "react-intl";
const DiskUsage = () => {
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
useEffect(() => {
systemService.getSystemInfo().then(setSystemInfo);
}, []);
if (!systemInfo) return null;
const usedPercentage = (systemInfo.used / systemInfo.total) * 100;
return (
<Stack spacing={2} style={{ width: 200 }}>
<Group position="apart">
<Text size="xs" weight={500}>
<FormattedMessage id="admin.shares.diskUsage" />
</Text>
<Text size="xs" color="dimmed">
{byteToHumanSizeString(systemInfo.used)} /{" "}
{byteToHumanSizeString(systemInfo.total)}
</Text>
</Group>
<Progress
value={usedPercentage}
size="sm"
radius="xl"
color={
usedPercentage > 90
? "red"
: usedPercentage > 70
? "yellow"
: undefined
}
/>
</Stack>
);
};
export default DiskUsage;
+1
View File
@@ -276,6 +276,7 @@ export default {
// /admin/shares
"admin.shares.title": "Share management",
"admin.shares.diskUsage": "Disk Usage",
"admin.shares.table.id": "Share ID",
"admin.shares.table.username": "Creator",
"admin.shares.table.visitors": "Visitors",
+4 -2
View File
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import Meta from "../../components/Meta";
import ManageShareTable from "../../components/admin/shares/ManageShareTable";
import DiskUsage from "../../components/admin/shares/DiskUsage";
import useTranslate from "../../hooks/useTranslate.hook";
import shareService from "../../services/share.service";
import { MyShare } from "../../types/share.type";
@@ -55,10 +56,11 @@ const Shares = () => {
return (
<>
<Meta title={t("admin.shares.title")} />
<Group position="apart" align="baseline" mb={20}>
<Title mb={30} order={3}>
<Group position="apart" align="center" mb={20}>
<Title mb={0} order={3}>
<FormattedMessage id="admin.shares.title" />
</Title>
<DiskUsage />
</Group>
<ManageShareTable
+5 -1
View File
@@ -57,7 +57,11 @@ const Upload = ({
try {
const isReverseShare = router.pathname != "/upload";
createdShare = await shareService.create(share, isReverseShare);
const totalSize = files.reduce((acc, file) => acc + file.size, 0);
createdShare = await shareService.create(
{ ...share, size: totalSize },
isReverseShare,
);
} catch (e) {
toast.axiosError(e);
setisUploading(false);
+12
View File
@@ -0,0 +1,12 @@
import api from "./api.service";
export interface SystemInfo {
used: number;
total: number;
}
const getSystemInfo = async (): Promise<SystemInfo | null> => {
return (await api.get("system/info")).data;
};
export default { getSystemInfo };
+1
View File
@@ -27,6 +27,7 @@ export type CreateShare = {
recipients: string[];
expiration: string;
security: ShareSecurity;
size?: number;
};
export type ShareMetaData = {