Compare commits

...

1 commit

Author SHA1 Message Date
Nicolas Meienberger
5a122706e5 wip 2023-03-05 10:55:40 +01:00
9 changed files with 214 additions and 197 deletions

View file

@ -15,4 +15,6 @@ NGINX_PORT_SSL=443
POSTGRES_PASSWORD=postgres
DOMAIN=tipi.localhost
STORAGE_PATH=/Users/nicolas/Projects/runtipi
REDIS_HOST=tipi-redis
REDIS_HOST=tipi-redis
DATABASE_URL=postgres://tipi:postgres@localhost:5432/tipi

View file

@ -19,7 +19,7 @@ COPY ./prisma/schema.prisma ./prisma/
RUN pnpm install -r --prefer-offline
COPY ./src ./src
# COPY ./src ./src
COPY ./esbuild.js ./esbuild.js
COPY ./tsconfig.json ./tsconfig.json
COPY ./next.config.mjs ./next.config.mjs

View file

@ -30,7 +30,7 @@
},
"dependencies": {
"@hookform/resolvers": "^2.9.10",
"@prisma/client": "^4.8.0",
"@prisma/client": "^4.11.0",
"@runtipi/postgres-migrations": "^5.3.0",
"@tabler/core": "1.0.0-beta16",
"@tabler/icons": "^1.109.0",
@ -40,6 +40,7 @@
"@trpc/react-query": "^10.11.1",
"@trpc/server": "^10.11.1",
"argon2": "^0.29.1",
"chokidar": "^3.5.3",
"clsx": "^1.1.1",
"express": "^4.17.3",
"fs-extra": "^10.1.0",
@ -112,7 +113,7 @@
"msw": "^1.0.0",
"next-router-mock": "^0.8.0",
"prettier": "^2.8.4",
"prisma": "^4.10.1",
"prisma": "^4.11.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "4.9.4",

View file

@ -4,7 +4,7 @@ specifiers:
'@babel/core': ^7.0.0
'@faker-js/faker': ^7.6.0
'@hookform/resolvers': ^2.9.10
'@prisma/client': ^4.8.0
'@prisma/client': ^4.11.0
'@runtipi/postgres-migrations': ^5.3.0
'@tabler/core': 1.0.0-beta16
'@tabler/icons': ^1.109.0
@ -34,6 +34,7 @@ specifiers:
'@typescript-eslint/eslint-plugin': ^5.47.1
'@typescript-eslint/parser': ^5.47.1
argon2: ^0.29.1
chokidar: ^3.5.3
clsx: ^1.1.1
dotenv-cli: ^6.0.0
esbuild: ^0.16.17
@ -63,7 +64,7 @@ specifiers:
node-fetch-commonjs: ^3.2.4
pg: ^8.7.3
prettier: ^2.8.4
prisma: ^4.10.1
prisma: ^4.11.0
react: 18.2.0
react-dom: 18.2.0
react-hook-form: ^7.38.0
@ -91,7 +92,7 @@ specifiers:
dependencies:
'@hookform/resolvers': 2.9.11_react-hook-form@7.43.1
'@prisma/client': 4.10.1_prisma@4.10.1
'@prisma/client': 4.11.0_prisma@4.11.0
'@runtipi/postgres-migrations': 5.3.0
'@tabler/core': 1.0.0-beta16_biqbaboplfbrettd7655fr4n2y
'@tabler/icons': 1.119.0_biqbaboplfbrettd7655fr4n2y
@ -101,6 +102,7 @@ dependencies:
'@trpc/react-query': 10.11.1_2pugaexxlcekgcbbhj75jqmiqi
'@trpc/server': 10.11.1
argon2: 0.29.1
chokidar: 3.5.3
clsx: 1.2.1
express: 4.18.2
fs-extra: 10.1.0
@ -173,7 +175,7 @@ devDependencies:
msw: 1.0.1_typescript@4.9.4
next-router-mock: 0.8.0_next@13.1.6+react@18.2.0
prettier: 2.8.4
prisma: 4.10.1
prisma: 4.11.0
ts-jest: 29.0.5_q5pvvsha5rrowzfbt33h5w23u4
ts-node: 10.9.1_awa2wsr5thmg3i7jqycphctjfq
typescript: 4.9.4
@ -192,6 +194,7 @@ packages:
dependencies:
'@jridgewell/gen-mapping': 0.1.1
'@jridgewell/trace-mapping': 0.3.17
dev: true
/@babel/code-frame/7.18.6:
resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
@ -202,6 +205,7 @@ packages:
/@babel/compat-data/7.20.14:
resolution: {integrity: sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/core/7.20.12:
resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==}
@ -233,6 +237,7 @@ packages:
'@babel/types': 7.20.7
'@jridgewell/gen-mapping': 0.3.2
jsesc: 2.5.2
dev: true
/@babel/helper-compilation-targets/7.20.7_@babel+core@7.20.12:
resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==}
@ -251,6 +256,7 @@ packages:
/@babel/helper-environment-visitor/7.18.9:
resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-function-name/7.19.0:
resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==}
@ -258,12 +264,14 @@ packages:
dependencies:
'@babel/template': 7.20.7
'@babel/types': 7.20.7
dev: true
/@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.20.7
dev: true
/@babel/helper-module-imports/7.18.6:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
@ -285,6 +293,7 @@ packages:
'@babel/types': 7.20.7
transitivePeerDependencies:
- supports-color
dev: true
/@babel/helper-plugin-utils/7.20.2:
resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==}
@ -296,12 +305,14 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.20.7
dev: true
/@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.20.7
dev: true
/@babel/helper-string-parser/7.19.4:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
@ -314,6 +325,7 @@ packages:
/@babel/helper-validator-option/7.18.6:
resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helpers/7.20.13:
resolution: {integrity: sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==}
@ -324,6 +336,7 @@ packages:
'@babel/types': 7.20.7
transitivePeerDependencies:
- supports-color
dev: true
/@babel/highlight/7.18.6:
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
@ -339,6 +352,7 @@ packages:
hasBin: true
dependencies:
'@babel/types': 7.20.7
dev: true
/@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.12:
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
@ -482,6 +496,7 @@ packages:
'@babel/code-frame': 7.18.6
'@babel/parser': 7.20.15
'@babel/types': 7.20.7
dev: true
/@babel/traverse/7.20.13:
resolution: {integrity: sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==}
@ -499,6 +514,7 @@ packages:
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
/@babel/types/7.20.7:
resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==}
@ -1123,6 +1139,7 @@ packages:
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jridgewell/gen-mapping/0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
@ -1131,6 +1148,7 @@ packages:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.14
'@jridgewell/trace-mapping': 0.3.17
dev: true
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
@ -1140,15 +1158,18 @@ packages:
/@jridgewell/set-array/1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
/@jridgewell/trace-mapping/0.3.17:
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jridgewell/trace-mapping/0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
@ -1201,6 +1222,7 @@ packages:
/@next/env/13.1.6:
resolution: {integrity: sha512-s+W9Fdqh5MFk6ECrbnVmmAOwxKQuhGMT7xXHrkYIBMBcTiOqNWhv5KbJIboKR5STXxNXl32hllnvKaffzFaWQg==}
dev: false
/@next/eslint-plugin-next/13.1.1:
resolution: {integrity: sha512-SBrOFS8PC3nQ5aeZmawJkjKkWjwK9RoxvBSv/86nZp0ubdoVQoko8r8htALd9ufp16NhacCdqhu9bzZLDWtALQ==}
@ -1371,8 +1393,8 @@ packages:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
/@prisma/client/4.10.1_prisma@4.10.1:
resolution: {integrity: sha512-VonXLJZybdt8e5XZH5vnIGCRNnIh6OMX1FS3H/yzMGLT3STj5TJ/OkMcednrvELgk8PK89Vo3aSh51MWNO0axA==}
/@prisma/client/4.11.0_prisma@4.11.0:
resolution: {integrity: sha512-0INHYkQIqgAjrt7NzhYpeDQi8x3Nvylc2uDngKyFDDj1tTRQ4uV1HnVmd1sQEraeVAN63SOK0dgCKQHlvjL0KA==}
engines: {node: '>=14.17'}
requiresBuild: true
peerDependencies:
@ -1381,17 +1403,18 @@ packages:
prisma:
optional: true
dependencies:
'@prisma/engines-version': 4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19
prisma: 4.10.1
'@prisma/engines-version': 4.11.0-57.8fde8fef4033376662cad983758335009d522acb
prisma: 4.11.0
dev: false
/@prisma/engines-version/4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19:
resolution: {integrity: sha512-tsjTho7laDhf9EJ9EnDxAPEf7yrigSMDhniXeU4YoWc7azHAs4GPxRi2P9LTFonmHkJLMOLjR77J1oIP8Ife1w==}
/@prisma/engines-version/4.11.0-57.8fde8fef4033376662cad983758335009d522acb:
resolution: {integrity: sha512-3Vd8Qq06d5xD8Ch5WauWcUUrsVPdMC6Ge8ILji8RFfyhUpqon6qSyGM0apvr1O8n8qH8cKkEFqRPsYjuz5r83g==}
dev: false
/@prisma/engines/4.10.1:
resolution: {integrity: sha512-B3tcTxjx196nuAu1GOTKO9cGPUgTFHYRdkPkTS4m5ptb2cejyBlH9X7GOfSt3xlI7p4zAJDshJP4JJivCg9ouA==}
/@prisma/engines/4.11.0:
resolution: {integrity: sha512-0AEBi2HXGV02cf6ASsBPhfsVIbVSDC9nbQed4iiY5eHttW9ZtMxHThuKZE1pnESbr8HRdgmFSa/Kn4OSNYuibg==}
requiresBuild: true
dev: true
/@redis/bloom/1.2.0_@redis+client@1.5.5:
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
@ -1477,6 +1500,7 @@ packages:
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
dependencies:
tslib: 2.5.0
dev: false
/@tabler/core/1.0.0-beta16_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-jg/IiGLGHeQive1b+DfOR8JA0DB/ihc/wIEMohLkVHplC/R66uYF/8zg5Ptk5OeV2XAkn+UaPwsfcdmmJR4utw==}
@ -2476,6 +2500,7 @@ packages:
electron-to-chromium: 1.4.301
node-releases: 2.0.10
update-browserslist-db: 1.0.10_browserslist@4.21.5
dev: true
/bs-logger/0.2.6:
resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
@ -3078,6 +3103,7 @@ packages:
/electron-to-chromium/1.4.301:
resolution: {integrity: sha512-bz00ASIIDjcgszZKuEA1JEFhbDjqUNbQ/PEhNEl1wbixzYpeTp2H2QWjsQvAL2T1wJBdOwCF5hE896BoMwYKrA==}
dev: true
/emittery/0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@ -3234,6 +3260,7 @@ packages:
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: true
/escape-html/1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@ -4333,6 +4360,7 @@ packages:
/immutable/4.2.4:
resolution: {integrity: sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==}
dev: false
/import-fresh/3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
@ -5206,6 +5234,7 @@ packages:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'}
hasBin: true
dev: true
/json-parse-even-better-errors/2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@ -5378,6 +5407,7 @@ packages:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
dependencies:
yallist: 3.1.1
dev: true
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
@ -6075,6 +6105,7 @@ packages:
/node-releases/2.0.10:
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
dev: true
/nopt/5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
@ -6498,13 +6529,14 @@ packages:
react-is: 18.2.0
dev: true
/prisma/4.10.1:
resolution: {integrity: sha512-0jDxgg+DruB1kHVNlcspXQB9au62IFfVg9drkhzXudszHNUAQn0lVuu+T8np0uC2z1nKD5S3qPeCyR8u5YFLnA==}
/prisma/4.11.0:
resolution: {integrity: sha512-4zZmBXssPUEiX+GeL0MUq/Yyie4ltiKmGu7jCJFnYMamNrrulTBc+D+QwAQSJ01tyzeGHlD13kOnqPwRipnlNw==}
engines: {node: '>=14.17'}
hasBin: true
requiresBuild: true
dependencies:
'@prisma/engines': 4.10.1
'@prisma/engines': 4.11.0
dev: true
/prompts/2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
@ -6706,6 +6738,7 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
dev: false
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
@ -6912,6 +6945,7 @@ packages:
chokidar: 3.5.3
immutable: 4.2.4
source-map-js: 1.0.2
dev: false
/saxes/6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
@ -6924,6 +6958,7 @@ packages:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
loose-envify: 1.4.0
dev: false
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
@ -7248,6 +7283,7 @@ packages:
'@babel/core': 7.20.12
client-only: 0.0.1
react: 18.2.0
dev: false
/stylis/4.1.3:
resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==}
@ -7653,6 +7689,7 @@ packages:
browserslist: 4.21.5
escalade: 3.1.1
picocolors: 1.0.0
dev: true
/uri-js/4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@ -7973,6 +8010,7 @@ packages:
/yallist/3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true
/yallist/4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}

View file

@ -52,6 +52,16 @@ model User {
@@map("user")
}
model event {
id Int @id @default(autoincrement())
status event_status_enum
message String @db.VarChar
type String @db.VarChar
args String[] @db.VarChar
createdAt DateTime @default(now()) @db.Timestamp(6)
updatedAt DateTime @default(now()) @db.Timestamp(6)
}
enum app_status_enum {
running
stopped
@ -67,3 +77,10 @@ enum update_status_enum {
FAILED
SUCCESS
}
enum event_status_enum {
running
success
error
waiting
}

View file

@ -4,6 +4,7 @@ source "${BASH_SOURCE%/*}/common.sh"
ROOT_FOLDER="${PWD}"
WATCH_FILE="${ROOT_FOLDER}/state/events"
WATCH_FOLDER="${ROOT_FOLDER}/state/test-events"
function clean_events() {
# Create the file if it doesn't exist
@ -24,9 +25,9 @@ function set_status() {
# Update the status of the event
if [[ "$(uname)" != "Linux" ]]; then
sed -i '' "s/${id} [a-z]*/${id} ${status}/g" "${WATCH_FILE}"
sed -i '' "s/${id} [a-z]*/${id} ${status}/g" "${WATCH_FOLDER}/${id}"
else
sed -i "s/${id}.*$/$(echo "${id} ${status}" | sed 's/\//\\\//g')/" "$WATCH_FILE"
sed -i "s/${id}.*$/$(echo "${id} ${status}" | sed 's/\//\\\//g')/" "${WATCH_FOLDER}/${id}"
fi
}
@ -107,14 +108,31 @@ function select_command() {
write_log "Listening for events in ${WATCH_FILE}..."
clean_events
# Listen in for changes in the WATCH_FILE
fswatch -0 "${WATCH_FILE}" | while read -d ""; do
# Read the command from the last line of the file
command=$(tail -n 1 "${WATCH_FILE}")
# fswatch -0 "${WATCH_FILE}" | while read -d ""; do
# # Read the command from the last line of the file
# command=$(tail -n 1 "${WATCH_FILE}")
# status=$(echo "$command" | cut -d ' ' -f 3)
# if [ -z "$command" ] || [ "$status" != "waiting" ]; then
# continue
# else
# select_command "$command"
# fi
# done
# Listen for new files in the folder
fswatch -0 "${WATCH_FOLDER}" | while read -d ""; do
# print the path
path="$REPLY"
command=$(tail -n 1 "${path}")
status=$(echo "$command" | cut -d ' ' -f 3)
if [ -z "$command" ] || [ "$status" != "waiting" ]; then
continue
else
select_command "$command"
select_command "$command"
fi
done

View file

@ -1,12 +1,15 @@
/* eslint-disable vars-on-top */
import cron from 'node-cron';
import fs from 'fs-extra';
import chokidar from 'chokidar';
import { event_status_enum } from '@prisma/client';
import { Logger } from '../Logger';
import { getConfig } from '../TipiConfig';
import { prisma } from '../../db/client';
declare global {
// eslint-disable-next-line no-var
var EventDispatcher: EventDispatcher | undefined;
var EventDispatcher: EventDispatcherClass | undefined;
}
export const EVENT_TYPES = {
@ -18,149 +21,59 @@ export const EVENT_TYPES = {
APP: 'app',
SYSTEM_INFO: 'system_info',
} as const;
export type EventType = (typeof EVENT_TYPES)[keyof typeof EVENT_TYPES];
type SystemEvent = {
id: string;
type: EventType;
args: string[];
creationDate: Date;
const WATCH_FOLDER = '/runtipi/state/test-events';
const getInfoFromFile = (path: string) => {
const file = fs.readFileSync(path, 'utf8');
const [type, id, status] = file.split(' ');
return { type, id, status };
};
const EVENT_STATUS = {
RUNNING: 'running',
SUCCESS: 'success',
ERROR: 'error',
WAITING: 'waiting',
} as const;
type EventStatus = (typeof EVENT_STATUS)[keyof typeof EVENT_STATUS];
const WATCH_FILE = '/runtipi/state/events';
// File state example:
// restart 1631231231231 running "arg1 arg2"
class EventDispatcher {
private static instance: EventDispatcher | null;
private dispatcherId = EventDispatcher.generateId();
private queue: SystemEvent[] = [];
private lock: SystemEvent | null = null;
private interval: NodeJS.Timer;
export class EventDispatcherClass {
private static instance: EventDispatcherClass | null;
private intervals: NodeJS.Timer[] = [];
private watcher = chokidar.watch(WATCH_FOLDER, {});
private prisma = prisma;
constructor() {
const timer = this.pollQueue();
this.interval = timer;
this.watcher.on('change', this.onFileChange);
}
public static getInstance(): EventDispatcher {
if (!EventDispatcher.instance) {
EventDispatcher.instance = new EventDispatcher();
private async onFileChange(path: string) {
Logger.info(`File ${path} has been changed`);
const { type, id, status } = getInfoFromFile(path);
Logger.info(`Event ${type} ${id} has status ${status}`);
if (!type || !id || !status) return;
if (status === 'success' || status === 'error') {
Logger.info(`Deleting event ${type} ${id} as it has status ${status}`);
console.log(prisma.event);
await this.prisma.event.delete({ where: { id: Number(id) } });
}
return EventDispatcher.instance;
Logger.info(`Updating event ${type} ${id} with status ${status}`);
await this.prisma.event.update({
where: { id: Number(id) },
data: { status: status as event_status_enum },
});
}
/**
* Generate a random task id
*
* @returns {string} id - Randomly generated id
*/
static generateId() {
return Math.random().toString(36).substring(2, 9);
}
/**
* Collect lock status and clean queue if event is done
*/
private collectLockStatusAndClean() {
if (!this.lock) {
return;
public static getInstance() {
if (!EventDispatcherClass.instance) {
EventDispatcherClass.instance = new EventDispatcherClass();
}
const status = this.getEventStatus(this.lock.id);
if (status === 'running' || status === 'waiting') {
return;
}
this.clearEvent(this.lock, status);
this.lock = null;
}
/**
* Poll queue and run events
*/
private pollQueue() {
Logger.info(`EventDispatcher(${this.dispatcherId}): Polling queue...`);
if (!this.interval) {
const id = setInterval(() => {
this.runEvent();
this.collectLockStatusAndClean();
}, 1000);
this.intervals.push(id);
return id;
}
return this.interval;
}
/**
* Run event from the queue if there is no lock
*/
private async runEvent() {
if (this.lock) {
return;
}
const event = this.queue[0];
if (!event) {
return;
}
this.lock = event;
// Write event to state file
const args = event.args.join(' ');
const line = `${event.type} ${event.id} waiting ${args}`;
fs.writeFileSync(WATCH_FILE, `${line}`);
}
/**
* Check event status
*
* @param {string} id - Event id
* @returns {EventStatus} - Event status
*/
private getEventStatus(id: string): EventStatus {
const event = this.queue.find((e) => e.id === id);
if (!event) {
return 'success';
}
// if event was created more than 3 minutes ago, it's an error
if (new Date().getTime() - event.creationDate.getTime() > 5 * 60 * 1000) {
return 'error';
}
const file = fs.readFileSync(WATCH_FILE, 'utf8');
const lines = file?.split('\n') || [];
const line = lines.find((l) => l.startsWith(`${event.type} ${event.id}`));
if (!line) {
return 'waiting';
}
const status = line.split(' ')[2] as EventStatus;
return status;
return EventDispatcherClass.instance;
}
/**
@ -168,19 +81,16 @@ class EventDispatcher {
*
* @param {EventType} type - Event type
* @param {[string]} args - Event arguments
* @returns {SystemEvent} event - Event object
* @returns {event} - The event created
*/
public dispatchEvent(type: EventType, args?: string[]): SystemEvent {
const event: SystemEvent = {
id: EventDispatcher.generateId(),
type,
args: args || [],
creationDate: new Date(),
};
public async dispatchEvent(type: EventType, args?: string[]) {
const newEvent = await this.prisma.event.create({
data: { type, status: 'waiting', message: '', args: args ? args.join(' ') : '' },
});
const line = `${newEvent.type} ${newEvent.id} waiting ${args}`;
fs.writeFileSync(`${WATCH_FOLDER}/${newEvent.id}`, `${line}`);
this.queue.push(event);
return event;
return newEvent;
}
/**
@ -189,19 +99,19 @@ class EventDispatcher {
* @param {SystemEvent} event - The event to clear
* @param {EventStatus} status - The status to consider the event to
*/
private clearEvent(event: SystemEvent, status: EventStatus = 'success') {
this.queue = this.queue.filter((e) => e.id !== event.id);
if (fs.existsSync(`/app/logs/${event.id}.log`)) {
const log = fs.readFileSync(`/app/logs/${event.id}.log`, 'utf8');
if (log && status === 'error') {
Logger.error(`EventDispatcher: ${event.type} ${event.id} failed with error: ${log}`);
} else if (log) {
Logger.info(`EventDispatcher: ${event.type} ${event.id} finished with message: ${log}`);
}
fs.unlinkSync(`/app/logs/${event.id}.log`);
}
fs.writeFileSync(WATCH_FILE, '');
}
// private clearEvent(event: SystemEvent, status: EventStatus = 'success') {
// this.queue = this.queue.filter((e) => e.id !== event.id);
// if (fs.existsSync(`/app/logs/${event.id}.log`)) {
// const log = fs.readFileSync(`/app/logs/${event.id}.log`, 'utf8');
// if (log && status === 'error') {
// Logger.error(`EventDispatcher: ${event.type} ${event.id} failed with error: ${log}`);
// } else if (log) {
// Logger.info(`EventDispatcher: ${event.type} ${event.id} finished with message: ${log}`);
// }
// fs.unlinkSync(`/app/logs/${event.id}.log`);
// }
// fs.writeFileSync(WATCH_FILE, '');
// }
/**
* Dispatch an event to the queue and wait for it to finish
@ -211,12 +121,14 @@ class EventDispatcher {
* @returns - Promise that resolves when the event is done
*/
public async dispatchEventAsync(type: EventType, args?: string[]): Promise<{ success: boolean; stdout?: string }> {
const event = this.dispatchEvent(type, args);
const event = await this.dispatchEvent(type, args);
const path = `${WATCH_FOLDER}/${event.id}`;
return new Promise((resolve) => {
const interval = setInterval(() => {
this.intervals.push(interval);
const status = this.getEventStatus(event.id);
let { status } = event;
do {
status = getInfoFromFile(path).status as event_status_enum;
let log = '';
if (fs.existsSync(`/app/logs/${event.id}.log`)) {
@ -224,26 +136,20 @@ class EventDispatcher {
}
if (status === 'success') {
clearInterval(interval);
resolve({ success: true, stdout: log });
} else if (status === 'error') {
clearInterval(interval);
resolve({ success: false, stdout: log });
}
}, 100);
} while (status !== 'success' && status !== 'error');
});
}
public clearInterval() {
clearInterval(this.interval);
this.intervals.forEach((i) => clearInterval(i));
}
public clear() {
this.queue = [];
this.lock = null;
EventDispatcher.instance = null;
fs.writeFileSync(WATCH_FILE, '');
public static clear() {
EventDispatcherClass.instance = null;
// Detlete all events from watch folder
fs.readdirSync(WATCH_FOLDER).forEach((file) => {
fs.unlinkSync(`${WATCH_FOLDER}/${file}`);
});
}
public scheduleEvent(params: { type: EventType; args?: string[]; cronExpression: string }) {
@ -255,7 +161,7 @@ class EventDispatcher {
}
}
export const EventDispatcherInstance = global.EventDispatcher || EventDispatcher.getInstance();
export const EventDispatcherInstance = global.EventDispatcher || EventDispatcherClass.getInstance();
if (getConfig().NODE_ENV !== 'production') {
global.EventDispatcher = EventDispatcherInstance;

View file

@ -9,6 +9,7 @@ import { Logger } from './core/Logger';
import { runPostgresMigrations } from './run-migration';
import { AppServiceClass } from './services/apps/apps.service';
import { prisma } from './db/client';
import { EventDispatcherClass } from './core/EventDispatcher/EventDispatcher';
let conf = {};
let nextApp: NextServer;
@ -43,7 +44,7 @@ nextApp.prepare().then(async () => {
app.listen(port, async () => {
const appService = new AppServiceClass(prisma);
EventDispatcher.clear();
EventDispatcherClass.clear();
// Run database migrations
await runPostgresMigrations();
@ -55,7 +56,10 @@ nextApp.prepare().then(async () => {
// Scheduled events
EventDispatcher.scheduleEvent({ type: 'update_repo', args: [getConfig().appsRepoUrl], cronExpression: '*/30 * * * *' });
EventDispatcher.scheduleEvent({ type: 'system_info', args: [], cronExpression: '* * * * *' });
EventDispatcher.scheduleEvent({ type: 'system_info', args: [], cronExpression: '*/5 * * * * *' });
// Every 5 sec
// EventDispatcher.scheduleEvent({ type: 'system_info', args: [], cronExpression: '*/5 * * * * *' });
appService.startAllApps();

View file

@ -0,0 +1,31 @@
-- Create enum for event type running, success, error, waiting if not exists
DO $$
BEGIN
IF NOT EXISTS (
SELECT
1
FROM
pg_type
WHERE
typname = 'event_status_enum') THEN
CREATE TYPE "public"."event_status_enum" AS ENUM (
'running',
'success',
'error',
'waiting'
);
END IF;
END
$$;
-- Create event table
CREATE TABLE IF NOT EXISTS "event" (
"id" serial NOT NULL,
"status" "public"."event_status_enum" NOT NULL,
"message" character varying NOT NULL,
"type" character varying NOT NULL,
"args" character varying[] NOT NULL,
"createdAt" timestamp NOT NULL DEFAULT now(),
"updatedAt" timestamp NOT NULL DEFAULT now(),
PRIMARY KEY ("id")
);