Running Version with indexDB

This commit is contained in:
Thomas Mack 2022-03-28 08:31:59 +02:00
parent 1c42a5d608
commit 8a6e300c47
23 changed files with 1461 additions and 138 deletions

206
package-lock.json generated
View File

@ -5,19 +5,23 @@
"requires": true,
"packages": {
"": {
"name": "billibox-vue",
"version": "0.1.0",
"dependencies": {
"@dsb-norge/vue-keycloak-js": "*",
"@fortawesome/fontawesome-pro": "^6.1.1",
"animate.css": "^4.1.1",
"bootstrap": "^5.1.3",
"core-js": "^3.8.3",
"fs": "^0.0.1-security",
"keycloak-js": "^17.0.1",
"uuid": "^8.3.2",
"vee-validate": "^4.5.10",
"vue": "^3.2.13",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.14",
"vuex": "^4.0.2"
"vue-validate": "^1.0.1",
"vuex": "^4.0.2",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.12.16",
@ -1642,7 +1646,6 @@
"version": "7.17.8",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.8.tgz",
"integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
@ -1698,6 +1701,31 @@
"node": ">=6.9.0"
}
},
"node_modules/@dsb-norge/vue-keycloak-js": {
"version": "2.1.3-beta",
"resolved": "https://registry.npmjs.org/@dsb-norge/vue-keycloak-js/-/vue-keycloak-js-2.1.3-beta.tgz",
"integrity": "sha512-RXnX/qwuUzNblxgQ8maxIKofhyQndpcIm8+egKonzSiTMAMUhyV8poSBoGEA3RCgcaxwOZhEw6rF0F39fc4BAg==",
"dependencies": {
"keycloak-js": "^15.0.2"
},
"peerDependencies": {
"vue": "^2.6.0 || >=3.0.0-rc.0"
}
},
"node_modules/@dsb-norge/vue-keycloak-js/node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"node_modules/@dsb-norge/vue-keycloak-js/node_modules/keycloak-js": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-15.1.1.tgz",
"integrity": "sha512-PPu70WfSI2CWX7GoF5AQ4HkqYJLTAOV/25wDG//9S5SUOhqIDxKjAv6P54hy8nKt2+rIZF2kqpv7FNEmBN2W4g==",
"dependencies": {
"base64-js": "1.3.1",
"js-sha256": "0.9.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@ -2095,6 +2123,11 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.180",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.180.tgz",
"integrity": "sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g=="
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.2.tgz",
@ -3430,6 +3463,8 @@
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -3441,7 +3476,9 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
"dev": true,
"optional": true,
"peer": true
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
@ -7302,8 +7339,12 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@ -7876,6 +7917,11 @@
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"node_modules/nanoid": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.1.tgz",
@ -9079,6 +9125,11 @@
"webpack": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
}
},
"node_modules/property-expr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -9268,8 +9319,7 @@
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
"dev": true
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"node_modules/regenerator-transform": {
"version": "0.14.5",
@ -10316,6 +10366,11 @@
"node": ">=0.6"
}
},
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
},
"node_modules/totalist": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/totalist/-/totalist-1.1.0.tgz",
@ -10595,7 +10650,6 @@
"version": "8.3.2",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
@ -10625,6 +10679,17 @@
"node": ">= 0.8"
}
},
"node_modules/vee-validate": {
"version": "4.5.10",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.5.10.tgz",
"integrity": "sha512-7dZE0PZTNY3Ztp6Gz8iw+QS7Fz59vU1qkD0rBJkldkf9Faw2lFjWgE0tiCist5RQkLgt7/HaVbojzb/SCdutfA==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.15"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue": {
"version": "3.2.31",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.31.tgz",
@ -10860,6 +10925,11 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"node_modules/vue-validate": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-validate/-/vue-validate-1.0.1.tgz",
"integrity": "sha1-M7YT7lmM2y8uNbQ9f59HxYbbILc="
},
"node_modules/vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
@ -11571,6 +11641,23 @@
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
"dev": true
},
"node_modules/yup": {
"version": "0.32.11",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
"integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
"dependencies": {
"@babel/runtime": "^7.15.4",
"@types/lodash": "^4.14.175",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
},
"engines": {
"node": ">=10"
}
}
},
"dependencies": {
@ -12686,7 +12773,6 @@
"version": "7.17.8",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.8.tgz",
"integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
@ -12730,6 +12816,30 @@
"to-fast-properties": "^2.0.0"
}
},
"@dsb-norge/vue-keycloak-js": {
"version": "2.1.3-beta",
"resolved": "https://registry.npmjs.org/@dsb-norge/vue-keycloak-js/-/vue-keycloak-js-2.1.3-beta.tgz",
"integrity": "sha512-RXnX/qwuUzNblxgQ8maxIKofhyQndpcIm8+egKonzSiTMAMUhyV8poSBoGEA3RCgcaxwOZhEw6rF0F39fc4BAg==",
"requires": {
"keycloak-js": "^15.0.2"
},
"dependencies": {
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"keycloak-js": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-15.1.1.tgz",
"integrity": "sha512-PPu70WfSI2CWX7GoF5AQ4HkqYJLTAOV/25wDG//9S5SUOhqIDxKjAv6P54hy8nKt2+rIZF2kqpv7FNEmBN2W4g==",
"requires": {
"base64-js": "1.3.1",
"js-sha256": "0.9.0"
}
}
}
},
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@ -13074,6 +13184,11 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.180",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.180.tgz",
"integrity": "sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g=="
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.2.tgz",
@ -13358,7 +13473,6 @@
"integrity": "sha512-vf4KqrmuOSnoEYGUiHPeMoxhh6wpiucLWXISn7xYFU80pK1lqcuhbl6tpurAanUIyRO/ENDUQBH7RAdbLNq1bA==",
"dev": true,
"requires": {
"@babel/core": "^7.12.16",
"@babel/helper-compilation-targets": "^7.12.16",
"@babel/helper-module-imports": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.13",
@ -13371,7 +13485,6 @@
"@vue/babel-plugin-jsx": "^1.0.3",
"@vue/babel-preset-jsx": "^1.1.2",
"babel-plugin-dynamic-import-node": "^2.3.3",
"core-js": "^3.8.3",
"core-js-compat": "^3.8.3",
"semver": "^7.3.4"
},
@ -14140,15 +14253,14 @@
"resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dev": true,
"requires": {
"ajv": "^8.0.0"
},
"requires": {},
"dependencies": {
"ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz",
"version": "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true,
"optional": true,
"peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -14160,7 +14272,9 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
"dev": true,
"optional": true,
"peer": true
}
}
},
@ -17229,8 +17343,12 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash.debounce": {
"version": "4.0.8",
@ -17697,6 +17815,11 @@
"thenify-all": "^1.0.0"
}
},
"nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"nanoid": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.1.tgz",
@ -18562,6 +18685,11 @@
"log-update": "^2.1.0"
}
},
"property-expr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -18718,8 +18846,7 @@
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
"dev": true
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"regenerator-transform": {
"version": "0.14.5",
@ -19583,6 +19710,11 @@
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true
},
"toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
},
"totalist": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/totalist/-/totalist-1.1.0.tgz",
@ -19791,8 +19923,7 @@
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"v8-compile-cache": {
"version": "2.3.0",
@ -19816,6 +19947,14 @@
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true
},
"vee-validate": {
"version": "4.5.10",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.5.10.tgz",
"integrity": "sha512-7dZE0PZTNY3Ztp6Gz8iw+QS7Fz59vU1qkD0rBJkldkf9Faw2lFjWgE0tiCist5RQkLgt7/HaVbojzb/SCdutfA==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.15"
}
},
"vue": {
"version": "3.2.31",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.31.tgz",
@ -20004,6 +20143,11 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vue-validate": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-validate/-/vue-validate-1.0.1.tgz",
"integrity": "sha1-M7YT7lmM2y8uNbQ9f59HxYbbILc="
},
"vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
@ -20562,6 +20706,20 @@
"dev": true
}
}
},
"yup": {
"version": "0.32.11",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
"integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
"requires": {
"@babel/runtime": "^7.15.4",
"@types/lodash": "^4.14.175",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
}
}
}
}

View File

@ -8,16 +8,21 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@dsb-norge/vue-keycloak-js": "*",
"@fortawesome/fontawesome-pro": "^6.1.1",
"animate.css": "^4.1.1",
"bootstrap": "^5.1.3",
"core-js": "^3.8.3",
"fs": "^0.0.1-security",
"keycloak-js": "^17.0.1",
"uuid": "^8.3.2",
"vee-validate": "^4.5.10",
"vue": "^3.2.13",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.14",
"vuex": "^4.0.2"
"vue-validate": "^1.0.1",
"vuex": "^4.0.2",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.12.16",

View File

@ -0,0 +1,51 @@
<template>
<nav class="navbar navbar-expand-lg bg-vue navbar-dark">
<div class="container">
<ul class="navbar-nav me-auto">
<li class="nav-item active">
<router-link to="/list" class="nav-link">Startseite</router-link>
</li>
<li class="nav-item">
<router-link to="/shop/item/create" class="nav-link">Neue Kamera</router-link>
</li>
<li class="nav-item">
<router-link to="/auth" class="nav-link">Auth</router-link>
</li>
</ul>
<div class="text-white" v-if="user && user.id"> angemeldet als: {{user.firstName}} {{user.lastName}} </div>
<button class="btn bg-vue2" @click="$keycloak.logoutFn" v-if="$keycloak.authenticated">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</div>
</nav>
</template>
<script>
import { mapGetters} from "vuex";
export default {
name: "TheNavbar",
computed: {
// ...mapGetters(["cartTotal"]),
...mapGetters({
user: "userProfile"
})
},
methods: {
async signOut() {
try {
await this.$store.dispatch("signout");
await this.$router.push("/");
} catch(e) {
console.log(e);
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,35 @@
<template>
<div>AuthRedirect</div>
<div class="container">
<p class=" row alert alert-info">{{queryInfo}}</p>
<p class="row alert alert-info">{{pathInfo}}</p>
</div>
</template>
<script>
export default {
name: "AuthRedirect",
beforeMount() {
console.log("Auth.beforeMount");
this.$keycloak.loadUserProfile().then( (res) => {
console.log("res", res);
});
},
computed: {
queryInfo() {
return this.$route.query
},
pathInfo() {
return this.$route.fullPath
},
}
}
</script>
<style scoped>
</style>

View File

@ -1,13 +1,146 @@
<template>
<div>Login User</div>
<div>
<div class="my-5 text-center offset-2 col-8">
<img src="~@fortawesome/fontawesome-pro/svgs/solid/user-lock.svg" class="img-fluid" />
</div>
<div class="text-center">
<h2>Jetzt anmelden</h2>
<p>
oder
<a class="text-vue2" role="button" @click="changeComponent('RegisterUser')"
>erstellen Sie ein Konto.</a
>
</p>
</div>
<div class="alert alert-danger col-md-8 offset-2" v-if="error"> {{ errorDisplayText }}</div>
<Form @submit="submitData" :validation-schema="schema" v-slot="{ errors }">
<div class="form-row">
<div class="form-group col-md-8 offset-2">
<label for="email"><strong>E-Mail-Adresse</strong></label>
<Field
as="input"
name="email"
type="email"
class="form-control"
id="email"
/>
<small class="text-danger" v-if="errors.email">{{
errors.email
}}</small>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-8 offset-2">
<label for="password"><strong>Passwort</strong></label>
<Field
as="input"
name="password"
type="password"
class="form-control"
id="password"
/>
<small class="text-danger" v-if="errors.password">{{
errors.password
}}</small>
</div>
</div>
<div class="form-row mt-3">
<div class="form-group col-md-8 offset-2">
<div class="d-grid">
<button class="btn bg-vue">
<span v-if="!isLoading">Einloggen</span>
<span v-else class="spinner-border spinner-border-sm"></span>
</button>
</div>
</div>
</div>
</Form>
<button class="btn bg-vue"
@click="keyCloakLogin"
>
<span v-if="!isLoading">Keycloak Login</span>
<span v-else class="spinner-border spinner-border-sm"></span>
</button>
</div>
</template>
<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";
export default {
name: "LoginUser"
name: "LoginUser",
components: {
Form,
Field,
},
emits: {
"change-component": (payload) => {
if (payload.componentName !== "RegisterUser") {
return false;
}
return true;
},
},
data() {
const schema = yup.object().shape({
email: yup
.string()
.required("E-Mail Adresse wird benötigt.")
.trim()
.email("Das ist keine gültige E-Mail Adresse"),
password: yup
.string()
.required("Ein Passwort wird benötigt.")
.min(6, "Das Passwort muss mindestens sechs Zeichen lang sein."),
});
return {
schema,
error: "",
isLoading: false
};
},
computed: {
errorDisplayText() {
return "Es ist ein Fehler aufgetreten"
}
},
methods: {
keyCloakLogin() {
this.$store.dispatch("login", {
})
},
submitData(values) {
console.log(values);
this.isLoading = true;
this.error = "";
this.$store.dispatch("signin", {
email: values.email,
password: values.password
})
.then(()=> {
this.isLoading = false;
this.$router.push({path: "/shop"});
})
.catch((error) => {
this.error = error.message;
this.isLoading = false;
}
)
},
changeComponent(componentName) {
this.$emit("change-component", { componentName });
},
}
}
</script>
<style scoped>
</style>

View File

@ -1,10 +1,143 @@
<template>
<div>RegisterUser</div>
<div>
<div class="my-5 text-center offset-2 col-8">
<img src="~@fortawesome/fontawesome-pro/svgs/solid/door-open.svg" class="img-fluid" />
</div>
<div class="text-center">
<h2>Jetzt registrieren</h2>
<p>
oder
<a class="text-vue2" role="button" @click="changeComponent('LoginUser')"
>melden Sie sich mit Ihrem Konto an</a
>
</p>
</div>
<div class="alert alert-danger col-md-8 offset-2" v-if="error"> {{ errorDisplayText }}</div>
<Form @submit="submitData" :validation-schema="schema" v-slot="{ errors }">
<div class="form-row">
<div class="form-group col-md-8 offset-2">
<label for="email"><strong>E-Mail-Adresse</strong></label>
<Field as="input" name="email" type="email" class="form-control" id="email" />
<small class="text-danger" v-if="errors.email">{{errors.email}}</small>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-8 offset-2">
<label for="password"><strong>Passwort</strong></label>
<Field type="password" name="password" class="form-control" id="password" />
<small class="text-danger" v-if="errors.password">{{errors.password}}</small>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-8 offset-2">
<label for="confirmPassword"
><strong>Passwort wiederholen</strong></label
>
<Field type="password" name="confirmPassword" class="form-control" id="confirmPassword" />
<small class="text-danger" v-if="errors.confirmPassword">{{errors.confirmPassword}}</small>
</div>
</div>
<div class="form-row mt-3">
<div class="form-group col-md-8 offset-2">
<div class="d-grid">
<button class="btn bg-vue">
<span v-if="!isLoading">Registrieren</span>
<span v-else class="spinner-border spinner-border-sm"></span>
</button>
<button class="btn bg-vue"
@click="keyCloakLogin"
>
<span v-if="!isLoading">Keycloak Login</span>
<span v-else class="spinner-border spinner-border-sm"></span>
</button>
</div>
</div>
</div>
</Form>
</div>
</template>
<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";
export default {
name: "RegiterUser"
name: "RegiterUser",
components: {
Form,
Field
},
emits: {
'change-component': (componentName) => {
if(componentName.componentName !== "LoginUser") {
return false;
}
return true;
}
}, data() {
const schema = yup.object().shape({
email: yup.string()
.required("E-Mail Adresse wird benötigt")
.trim()
.email("Das ist keine gültige E-Mail-Adresse"),
password: yup.string()
.required("Ein Passwort wird benötigt")
.trim()
.min(6, "Das Passwort muss mindestens 6 Zeichen lang sein"),
confirmPassword: yup.string().required("Bitte wiederholen Sie Ihr Passwort")
.oneOf([yup.ref("password")], "Passwörter stimmen nicht überein")
});
return {
schema,
error: "",
isLoading: false
};
},
computed: {
errorDisplayText() {
if(this.error) {
if(this.error.includes("EMAIL_EXISTS")) {
return "Die E-Mail Adresse existiert bereits."
}
}
return "Es ist ein Fehler aufgetreten"
}
},
methods: {
keyCloakLogin() {
this.$store.dispatch("login", {
})
},
submitData(values) {
console.log(values);
this.isLoading = true;
this.error = "";
this.$store.dispatch("signup", {
email: values.email,
password: values.password
}) .then(()=> {
this.isLoading = false;
console.log(this.$store.state);
this.changeComponent("LoginUser")
})
.catch((error) => {
this.error = error.message;
this.isLoading = false;
}
)
},
changeComponent(componentName) {
this.$emit("change-component", {componentName})
;
},
}
}
</script>

View File

@ -0,0 +1,124 @@
<template>
<transition
enter-active-class="animate__animated animate__lightSpeedInRight"
mode="out-in"
appear
>
<div class="row" :key="activeCamera.id">
<div class="col-12">
<h1 class="mt-4">
Bearbeiten
<button class="btn btn-lg bg-vue float-end"
@click="leaveEditmode"
>Verwerfen</button>
</h1>
<Form @submit="submitData" :validation-schema="schema" v-slot="{ errors }">
<div class="card mt-4">
<div class="row no-gutters">
<div class="col-md-4">
<img
src="https://dummyimage.com/600x400/34495e/fff"
class="card-img"
/>
</div>
<div class="col-md-8">
<div class="card-body">
<div class="row">
<div class="col-9">
<label for="name">Name der Kamera</label>
<Field as="input" name="name" :value="activeCamera.name" type="text" class="form-control" id="name"></Field>
<small class="text-danger" v-if="errors.name">{{errors.name}}</small>
</div>
<div class="col-3">
<div class="d-grid">
<button class="btn bg-vue2"
> </button>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
Lorem
</div>
</div>
</div>
</div>
</div>
</div>
<button class="btn btn-lg bg-vue float-end mx-3"
>Speichern</button>
</Form>
</div>
</div>
</transition>
</template>
<script>
import { mapGetters} from "vuex";
import { Form, Field } from "vee-validate";
import * as yup from "yup";
export default {
name: "CameraReadDetail",
components: {
Form,
Field
},
data() {
const schema = yup.object().shape({
name: yup.string()
.required("Name wird benötigt")
.trim()
});
return {
schema,
error: "",
isLoading: false
}
},
computed: {
...mapGetters(["activeCamera"]),
errorDisplayText() {
if(this.error) {
return "Es ist ein Fehler aufgetreten"
}
return "Es ist ein irgendein Fehler aufgetreten"
}
},
methods: {
submitData(values) {
console.log("SubmitData", values);
this.$store.dispatch("storeCamera", {
active:false,
brand_key:"pentax",
buildtype:"slr",
description:"Langer Text",
edit:false,
name: values.name,
shape_key:"neuwertig",
year_of_production:2000,
year_of_purchase:2022,
});
},
leaveEditmode() {
this.$store.dispatch("setCameraEditState", {cameraId:this.activeCamera.id, edit:false})
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,27 +1,26 @@
<template>
<div class="container">
<div class="container ">
<div class="">
Wir haben {{cameras.length}} in der Liste
Wir haben {{ cameras.length }} in der Liste
<input type="text" name="searchstring" v-model="searchText">
</div>
</div>
<section>
<div v-for="camera in filteredCameras" :key="camera.id">
<transition-group
enter-active-class="animate__animated animate__bounceInRight"
leave-active-class="animate__animated animate__bounceOutRight"
mode="out-in"
>
<component
:is="componentName"
:camera=camera
:key="camera.id"
></component>
</transition-group>
</div>
</section>
</div>
</template>
<script>
import CameraListItem from "@/components/camera/CameraListItem";
export default {
name: "CameraList",
components: {
@ -29,24 +28,29 @@ export default {
},
props: {
cameras: {
type:Object,
type: Object,
required: true
}
},
data() {
return {
searchText: ""
searchText: "",
orderBy: "name",
orderDirection: "acs"
}
},
computed:{
computed: {
componentName() {
return "CameraListItem";
},
filteredCameras() {
if(this.searchText ==="") return this.cameras ;
else return this.cameras.filter( (camera) => camera.name.toLowerCase().includes(this.searchText.toLowerCase()));
if (this.searchText === "") return this.cameras;
else {
return this.cameras.filter((camera) => camera.name.toLowerCase().includes(this.searchText.toLowerCase()));
}
}
}
}
</script>

View File

@ -1,13 +1,29 @@
<template>
<div class="card mt-3"
@click="toggleActive">
<div class="card-header"
<transition
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
mode="out-in"
appear
>
<div class="card mt-3"
@click="toggleActive"
:key="camera.id"
>
<div class="card-header col-md-12"
:class="getActiveStyle"
>{{ camera.name }}</div>
<div class="card-body">
>
<div class="row">
<div class="col-md-8">{{ camera.name }}</div>
<div class="col-md-4 text-end">{{ camera.year_of_purchase }}</div>
</div>
</div>
<div class="card-body" :class="`${getActiveStyle}`">
{{camera.year_of_production}}
</div>
</div>
</div>
</transition>
</template>
<script>

View File

@ -0,0 +1,72 @@
<template>
<transition
enter-active-class="animate__animated animate__lightSpeedInRight"
mode="out-in"
appear
>
<div class="row" :key="activeCamera.id">
<div class="col-12">
<h1 class="mt-4">
Neuer Artikel
<button class="btn btn-lg bg-vue float-end"
@click="editCamera"
>Bearbeiten</button>
</h1>
<div class="card mt-4">
<div class="row no-gutters">
<div class="col-md-4">
<img
src="https://dummyimage.com/600x400/34495e/fff"
class="card-img"
/>
</div>
<div class="col-md-8">
<div class="card-body">
<div class="row">
<div class="col-9">
<h5 class="card-title mb-4">{{ activeCamera.name }}</h5>
</div>
<div class="col-3">
<div class="d-grid">
<button class="btn bg-vue2"
> </button>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
Lorem
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import { mapGetters} from "vuex";
export default {
name: "CameraReadDetail",
computed: {
...mapGetters(["activeCamera"]),
},
methods: {
editCamera() {
this.$store.dispatch("setCameraEditState", {cameraId:this.activeCamera.id, edit:true})
}
}
}
</script>
<style scoped>
</style>

154
src/indexdDB/index.ts Normal file
View File

@ -0,0 +1,154 @@
export default class BilliDB {
private DB_VERSION: number;
private DB_NAME: string;
constructor(dbName:string, dbVersion:number) {
this.DB_NAME = dbName;
this.DB_VERSION = dbVersion;
}
async getDb():Promise<IDBOpenDBRequest> {
return new Promise((resolve, reject) => {
const request = window.indexedDB.open(this.DB_NAME, this.DB_VERSION);
request.onerror = e => {
console.log("Error opening db", e);
reject("Error");
}
request.onsuccess =e => {
console.log("connect erfolgreich");
// @ts-ignore
resolve(e.target.result)
}
request.onupgradeneeded = e => {
console.log("onupgradeneeded");
// @ts-ignore
const db = e.target.result;
db.createObjectStore("cameras", {autoIncrement: false, keyPath: 'id'});
db.createObjectStore("brands", {autoIncrement: false, keyPath: 'schluessel'});
db.createObjectStore("conditions", {autoIncrement: false, keyPath: 'schluessel'});
db.createObjectStore("buildtypes", {autoIncrement: false, keyPath: 'schluessel'});
}
})
}
async saveItem(item, storeId:string) {
const db = await this.getDb();
return new Promise((resolve, reject) => {
console.log("Starting Add Transaction");
// @ts-ignore
const trans = db.transaction([storeId], 'readwrite');
trans.oncomplete = (e) => {
resolve(e);
};
trans.onerror = (e) => {
console.log("on Error", e)
reject(e);
};
const store = trans.objectStore(storeId);
store.add(item);
store.onerror = (e) => {
console.log("Error on Store", e)
}
store.oncomplete = (e) => {
console.log("oncomplete on Store", e)
}
store.onsuccess = (e) => {
console.log("onsuccess on Store", e)
}
})
}
async saveItems(items, storeId:string) {
const db = await this.getDb();
return new Promise((resolve, reject) => {
console.log("Starting Add Transaction");
// @ts-ignore
const trans = db.transaction([storeId], 'readwrite');
trans.oncomplete = (e) => {
resolve(e);
};
trans.onerror = (e) => {
console.log("on Error", e)
reject(e);
};
const store = trans.objectStore(storeId);
items.forEach((item) => {
store.add(item);
})
store.onerror = (e) => {
console.log("Error on Store", e)
}
store.oncomplete = (e) => {
console.log("oncomplete on Store", e)
}
store.onsuccess = (e) => {
console.log("onsuccess on Store", e)
}
})
}
async getItems(storeId:string) {
console.log("DB-getItems", storeId)
const db = await this.getDb();
return new Promise((resolve, reject) => {
console.log("DB-getItems: Starting Get Transaction");
const items = [];
// @ts-ignore
const trans = db.transaction([storeId], 'readonly');
trans.oncomplete = (e) => {
resolve(items);
};
trans.onerror = (e) => {
console.log("on Error", e)
reject(e);
};
const store = trans.objectStore(storeId);
console.log("--> We have try to get a cursor");
store.openCursor().onsuccess = (e) => {
console.log("--> We have a cursor", e.target.result);
const cursor = e.target.result;
if(cursor) {
items.push(cursor.value);
cursor.continue();
}
console.log("onsuccess on getAll", items)
// resolve(e.target.result);
}
store.onerror = (e) => {
console.log("Error on getAll", e)
}
store.oncomplete = (e) => {
console.log("oncomplete on getAll", e)
}
})
}
}

View File

@ -0,0 +1,61 @@
<template>
<div>
<TheNavbar></TheNavbar>
<main>
<div class="container-fluid" :class="containerClasses">
<div class="row" :class="rowClasses">
<div :class="leftColumnClasses">
<slot name="leftCol">
<h1>Linke Spalte</h1>
</slot>
</div>
<div :class="rightColumnClasses">
<slot name="rightCol"><h1>Rechte Spalte</h1></slot>
</div>
</div>
</div>
</main>
</div>
</template>
<script>
import TheNavbar from "@/components/TheNavbar";
export default {
name: "TheShopLayout",
components: {
TheNavbar
},
props: {
leftColumnClass: {
type:String,
default: 'col-md-8'
},
rightColumnClass: {
type:String,
default: 'col-md-4'
},
fullsize: {
type: Boolean,
default: false,
}
},
computed: {
leftColumnClasses() {
return [this.leftColumnClass, this.fullsize ? "h-100": ""];
},
rightColumnClasses() {
return [this.rightColumnClass, this.fullsize ? "h-100": ""];
},
rowClasses(){
return [this.fullsize ? "h-100": ""];
},
containerClasses(){
return [this.fullsize ? "vh-100": ""];
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,9 +1,37 @@
import { createApp } from 'vue';
import Vue from 'vue';
import App from './App.vue';
import store from './store';
import router from './router';
createApp(App)
.use(store)
.use(router)
.mount('#app');
//# sourceMappingURL=main.js.map
import Keycloak from "keycloak-js";
const initOptions = {
url: 'http://auth.toking.de/', realm: 'toking', clientId: 'billibox', onLoad: 'login-required',
redirectUri: 'http://127.0.0.1:8080/'
};
//const app = createApp(App);
const keycloak = Keycloak(initOptions);
keycloak.init({ onLoad: initOptions.onLoad }).then((auth) => {
if (!auth) {
window.location.reload();
}
else {
Vue.$log.info("Authenticated");
new Vue({
el: '#app',
render: h => h(App, { props: { keycloak: keycloak } })
});
}
//Token Refresh
setInterval(() => {
keycloak.updateToken(70).then((refreshed) => {
if (refreshed) {
Vue.$log.info('Token refreshed' + refreshed);
}
else {
Vue.$log.warn('Token not refreshed, valid for '
+ Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
}
}).catch(() => {
Vue.$log.error('Failed to refresh token');
});
}, 6000);
}).catch(() => {
Vue.$log.error("Authenticated Failed");
});

View File

@ -2,9 +2,97 @@ import { createApp } from 'vue'
import App from './App.vue'
import store from './store';
import router from './router'
import VueKeyCloak from '@dsb-norge/vue-keycloak-js'
import { KeycloakInstance } from "keycloak-js";
import { VueKeycloakInstance } from "@dsb-norge/vue-keycloak-js/dist/types";
const app =
createApp(App);
createApp(App)
.use(store)
.use(router)
.mount('#app')
app.use(store)
app.use(router)
app.use(VueKeyCloak, {
config: {
url: 'https://auth.toking.de/',
realm: 'toking',
clientId: 'billibox',
redirectUri: 'http://127.0.0.1:8080/',
onLoad: 'login-required',
enableLogging: true,
scope: 'open-id'
},
init: {
onLoad: 'login-required'
},
onReady (keycloak: KeycloakInstance) {
store.dispatch("storeKeycloak", keycloak).then( (res) => {
keycloak.loadUserProfile().then( (res) => {
store.dispatch("storeUserProfile", res);
console.log("res", res);
});
}
)
}
})
app.mount('#app')
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$keycloak: VueKeycloakInstance
}
}
// import Vue from 'vue';
// import App from './App.vue';
// import Keycloak from "keycloak-js";
// const initOptions = {
// url: 'https://auth.toking.de/', realm: 'toking', clientId: 'billibox', onLoad: 'login-required',
// redirectUri: 'http://127.0.0.1:8080/'
// };
// //const app = createApp(App);
// const keycloak = Keycloak(initOptions);
// keycloak.init({ onLoad: 'login-required', redirectUri: 'http://127.0.0.1:8080/' }).then((auth) => {
// if (!auth) {
// window.location.reload();
// }
// else {
// // @ts-ignore
// console.info("Authenticated", auth);
// // @ts-ignore
//
// new Vue({
// el: '#app',
// render: h => h(App, { props: { keycloak: keycloak } })
// });
// }
// //Token Refresh
// // setInterval(() => {
// // keycloak.updateToken(70).then((refreshed) => {
// // if (refreshed) {
// // // @ts-ignore
// //
// // console.info('Token refreshed' + refreshed);
// // }
// // else {
// // // @ts-ignore
// //
// // console.warn('Token not refreshed, valid for '
// // + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
// // }
// // }).catch(() => {
// // // @ts-ignore
// //
// // console.error('Failed to refresh token');
// // });
// // }, 6000);
// }).catch(() => {
// // @ts-ignore
//
// console.error("Authenticated Failed");
// });

View File

@ -1,4 +1,5 @@
<template>
<TheNavbar></TheNavbar>
<TheTwoColumnsLayout
:left-column-class="'col-md-8 bg-vue'"
:right-column-class="'col-md-4'"
@ -37,10 +38,12 @@
import TheTwoColumnsLayout from "@/layouts/TheTwoColumnsLayout";
import RegisterUser from "@/components/auth/RegisterUser";
import LoginUser from "@/components/auth/LoginUser";
import TheNavbar from "@/components/TheNavbar";
export default {
name: "HomePage",
components: {
TheNavbar,
TheTwoColumnsLayout,
RegisterUser,
LoginUser
@ -59,5 +62,5 @@ export default {
</script>
<style scoped>
svg { color: white};
svg { color: white}
</style>

View File

@ -1,59 +1,70 @@
<template>
<TheTwoColumnsLayout>
<TheCameraLayout
:left-column-class="'col-md-8 '"
:right-column-class="'col-md-4 bg-vue'"
:fullsize="true"
>
<template #leftCol>
<div class="mt-5 text-center">
<div class="display-1 my-5">Billibox</div>
<div class="display-4 my-5">Die kleine Welt alter Fotoapparate</div>
<div class="my-5 offset-4 col-4">
<img src="~@fortawesome/fontawesome-pro/svgs/duotone/camera.svg" class="img-fluid">
<div v-if="activeCamera && activeCamera.edit">
<CameraEditDetail></CameraEditDetail>
</div>
<div v-else-if="activeCamera">
<CameraReadDetail></CameraReadDetail>
</div>
<div v-else>
<h2>Bitte wählen sie eine Kamera aus der Liste</h2>
</div>
</template>
<template #rightCol>
<transition
enter-active-class="animate__animated animate__bounceInRight"
leave-active-class="animate__animated animate__bounceOutRight"
mode="out-in"
>
<CameraList :cameras="cameras"></CameraList>
</transition>
</template>
</TheTwoColumnsLayout>
</TheCameraLayout>
</template>
<script>
import TheTwoColumnsLayout from "@/layouts/TheTwoColumnsLayout";
import TheCameraLayout from "@/layouts/TheCameraLayout";
import CameraList from "@/components/camera/CameraList";
import CameraReadDetail from "@/components/camera/CameraReadDetail";
import CameraEditDetail from "@/components/camera/CameraEditDetail";
import {mapGetters} from "vuex";
export default {
name: "ReadListPage",
components: {
CameraList,
TheCameraLayout,
CameraReadDetail,
CameraEditDetail
},
computed: {
...mapGetters(["activeCamera"]),
storeIsInitialized( ) {
return this.$store.getters.isInitialized;
},
cameras() {
return this.$store.getters.cameras;
}
},
components: {
CameraList,
TheTwoColumnsLayout
},
beforeCreate() {
console.log("beforeCreate", this.$store.getters.isInitialized);
if(!this.$store.getters.isInitialized) {
console.log("Not yet initialized")
this.$store.dispatch("initialize");
this.$store.dispatch("initialize", {seed: true});
}
},
data() {
return {
}
}
},
}
</script>

View File

@ -21,9 +21,26 @@ const router = createRouter( {
});
router.beforeEach((to, from, next) => {
if(to.meta.requiresAuth && !store.getters.isAuthenticated) {
next("/")
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
router.beforeEach(async(to, from, next) => {
if(to.meta.requiresAuth) {
// @ts-ignore
const kc = store.getters.keycloak;
console.log(kc);
while(typeof kc == "undefined" || kc.createLoginUrl === null) {
await sleep(100);
}
if(kc.authenticated) {
next()
}
else {
const loginUrl = kc.createLoginUrl()
window.location.replace(loginUrl)
}
} else {
next();
}

View File

@ -1,6 +1,7 @@
import store from "@/store";
import ReadCameraPage from "@/pages/ReadCameraPage.vue";
import ReadListPage from "@/pages/ReadListPage.vue";
import AuthRedirect from '@/components/auth/AuthRedirect.vue'
const appRoutes = [
@ -9,13 +10,13 @@ const appRoutes = [
alias: "/home",
name: "home",
component: () => import('@/pages/HomePage.vue'),
beforeEnter: (to:any, from:any, next:any) => {
if (store.getters.isAuthenticated) {
next("/shop");
} else {
next();
}
}
// beforeEnter: (to:any, from:any, next:any) => {
// if (store.getters.isAuthenticated) {
// next("/list");
// } else {
// next();
// }
// }
},
{
path: "/read/cameara/:id",
@ -37,7 +38,24 @@ const appRoutes = [
requiresAuth: false,
// enterTransition: "rubberBand"
}
},
{
path: "/auth",
component: AuthRedirect,
props: true,
meta: {
requiresAuth: true,
// enterTransition: "rubberBand"
}
},
{
path: "/profile",
component: AuthRedirect,
props: true,
meta: {
requiresAuth: true,
// enterTransition: "rubberBand"
}
},
]

View File

@ -1,3 +1,4 @@
export const shape = [
{ schluessel: "neu", name: "neu", beschreibung: '' },
{ schluessel: "neuwertig", name: "neuwertig", beschreibung: '' },
@ -209,6 +210,36 @@ export const cameras = [
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 2022,
},
{
id: "agfa_01",
brand_key: "agfa",
shape_key: "neuwertig",
name: "Agfa Click",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 2000,
},
{
id: "agfa_02",
brand_key: "agfa",
shape_key: "neuwertig",
name: "Agfa Box",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 1980,
},
{
id: "cancon_02",
brand_key: "canon",
shape_key: "neuwertig",
name: "Canon EOS",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 1980,
}
];
//# sourceMappingURL=seed.js.map

View File

@ -1,6 +1,7 @@
export const SEED_VERSION = 1;
export const shape = [
export const conditions = [
{schluessel: "neu", name: "neu", beschreibung: ''},
{schluessel: "neuwertig", name: "neuwertig", beschreibung: ''},
{schluessel: "lg", name: "leichte Gebrauchsspuren", beschreibung: ''},
@ -215,6 +216,66 @@ export const cameras = [
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 2022,
},
{
id: "agfa_01",
brand_key: "agfa",
shape_key: "neuwertig",
name: "Agfa Click",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 2000,
},
{
id: "agfa_02",
brand_key: "agfa",
shape_key: "neuwertig",
name: "Agfa Box",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 1980,
},
{
id: "cancon_02",
brand_key: "canon",
shape_key: "neuwertig",
name: "Canon EOS",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 1980,
},
{
id: "agfa_03",
brand_key: "agfa",
shape_key: "neuwertig",
name: "Agfa Click",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 2000,
},
{
id: "agfa_05",
brand_key: "agfa",
shape_key: "neuwertig",
name: "Agfa Box",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 1980,
},
{
id: "cancon_07",
brand_key: "canon",
shape_key: "neuwertig",
name: "Canon EOS",
buildtype: "slr",
description: "Langer Text",
year_of_production: 2000,
year_of_purchase: 1980,
}
]

View File

@ -1,28 +1,64 @@
import {ActionContext} from "vuex";
import Keycloak from "keycloak-js";
const keyInitOptions = {
url: 'https://auth.toking.de/',
realm: 'toking',
clientId: 'billibox',
redirectUri: 'http://127.0.0.1:8080/',
onLoad: 'login-required',
enableLogging: true,
scope: 'open-id'
}
const state = {
userId: null,
token: null
keycloak: null,
userProfile: null,
};
const getters = {
isAuthenticated(state:any ) {
return state.token
return state.keycloak.authenticated
},
token: (state:any) => state.token
token: (state:any) => state.token,
keycloak: (state:any) => state.keycloak,
userProfile: (state:any) => state.userProfile
};
const mutations = {
setUser(context:ActionContext<any, any>, payload:any) {
console.log("mutations.setUser", payload);
state.userId = payload.userId;
state.token = payload.token;
storeKeycloak(context:ActionContext<any, any>, payload:any) {
console.log("mutations.storeKeycloak");
state.keycloak = payload;
},
storeUserProfile(context:ActionContext<any, any>, payload:any) {
state.userProfile = payload
}
};
const actions = {};
const actions = {
storeKeycloak(context:ActionContext<any, any>, payload:any) {
console.log("action.storeKeycloak");
context.commit("storeKeycloak", payload);
},
getToken(context:any, payload:any) {
console.log("actions.getToken")
console.log("Authenticated", context.state.keycloak)
context.state.keycloak.updateToken(70).then((refreshed) => {
console.log("Refresehd Token", refreshed);
}).catch((err)=> {
console.log("Error", err);
})
},
storeUserProfile(context:ActionContext<any, any>, payload:any) {
context.commit("storeUserProfile", payload)
}
};
const modules = {};
const authModule = {

View File

@ -1,24 +1,34 @@
import {ActionContext} from "vuex";
import * as seed from "@/seed";
import BilliDB from "@/indexdDB";
import { v4 as uuidv4 } from 'uuid';
import {SEED_VERSION} from "@/seed";
const db = new BilliDB("billibox", 3);
const state = {
isInitialized: false,
cameras: [],
brands: [],
shapes: [],
conditions: [],
buildtypes: []
};
const getters = {
cameras: (state: any) => state.cameras,
cameras: (state: any) => {
return state.cameras.sort();
},
camera: (state: any) => (id: any) => state.cameras.find((camera: any) => camera.id === id),
activeCamera: (state: any) => state.cameras.find((camera:any) => camera.active ),
brands: (state: any) => state.brands,
brand: (state: any) => (id: any) => state.brands.find((brand: any) => brand.schluessel === id),
shapes: (state: any) => state.shapes,
shape: (state: any) => (id: any) => state.shapes.find((shape: any) => shape.schluessel === id),
conditions: (state: any) => state.conditions,
condition: (state: any) => (id: any) => state.conditions.find((condition: any) => condition.schluessel === id),
buildtypes: (state: any) => state.buildtypes,
buildtype: (state: any) => (id: any) => state.buildtypes.find((buildtype: any) => buildtype.schluessel === id),
@ -39,25 +49,47 @@ const mutations = {
})
console.log("STORE", state.cameras);
},
storeCamera(sate:any, payload:any) {
// console.log("mutation.storeCamera", payload)
const objCamera = state.cameras.find((camera:any) => camera.id === payload.id);
if(objCamera) {
// console.log("found existing Camera", objCamera.id);
payload.edit =false;
objCamera.name = payload.name;
} else {
// console.log("creating new Camera");
payload.edit = false;
state.cameras.push(payload);
}
return payload.id;
},
setCameraActiveState(state:any, payload:any) {
// console.log("setCameraActiveState", payload);
state.cameras.map((camera:any) => camera.active= false);
const objCamera = state.cameras.find((camera:any) => camera.id === payload.id);
objCamera.active = payload.active;
},
setBrands(state:any, payload:any) {
console.log("mutation.setBrands");
state.brands = payload;
console.log("STORE", state.brands);
setCameraEditState(state:any, payload:any) {
state.cameras.map((camera:any) => camera.edit= false);
const objCamera = state.cameras.find((camera:any) => camera.id === payload.id);
objCamera.edit = payload.edit;
},
setShapes(state:any, payload:any) {
console.log("mutation.setShapes");
state.shapes = payload;
console.log("STORE", state.shapes);
setBrands(state:any, payload:any) {
// console.log("mutation.setBrands");
state.brands = payload;
// console.log("STORE", state.brands);
},
setConditions(state:any, payload:any) {
//console.log("mutation.setConditions");
state.conditions = payload;
//console.log("STORE", state.conditions);
},
setBuildtypes(state:any, payload:any) {
console.log("mutation.setBuildtypes");
//console.log("mutation.setBuildtypes");
state.buildtypes = payload;
console.log("STORE", state.buildtypes);
//console.log("STORE", state.buildtypes);
},
setInitialized(state:any, payload:boolean) {
state.isInitialized = payload;
@ -65,7 +97,12 @@ const mutations = {
};
const actions = {
fetchCameras(context:ActionContext<any, any>) {
context.commit("setCameras", seed.cameras);
console.log("action.fetchCameras")
db.getItems("cameras").then((cameras) => {
console.log("From DB", cameras);
context.commit("setCameras", cameras);
})
},
fetchBrands(context:ActionContext<any, any>) {
context.commit("setBrands", seed.brands);
@ -73,8 +110,8 @@ const actions = {
fetchBuildTypes(context:ActionContext<any, any>) {
context.commit("setBuildtypes", seed.buildtype);
},
fetchShape(context:ActionContext<any, any>) {
context.commit("setShapes", seed.shape);
fetchCondition(context:ActionContext<any, any>) {
context.commit("setConditions", seed.conditions);
},
setCameraActiveState(context:ActionContext<any, any>, payload:any) {
console.log("action.setCameraActiveState", payload)
@ -82,12 +119,58 @@ const actions = {
context.commit("setCameraActiveState", {id: payload.cameraId, active:payload.active});
},
initialize(context:ActionContext<any, any>) {
setCameraEditState(context:ActionContext<any, any>, payload:any) {
console.log("action.setCameraEditState", payload)
const cameraObj = state.cameras.find( (camera:any) => camera.id === payload.cameraId);
context.commit("setCameraEditState", {id: payload.cameraId, edit:payload.edit});
},
storeCamera(context:ActionContext<any, any>, payload:any) {
// console.log("action.storeCamera", payload)
if(!payload.id) payload.id = uuidv4();
//console.log(" ... with id ", payload.id);
//console.log("Try to save to indexDB");
db.saveItem(payload , "cameras").then( (res) => {
console.log("DB-Save resolved", res )}
)
context.commit("storeCamera", payload);
context.commit("setCameraActiveState", {cameraId:payload.id, active:true})
},
initialize(context:ActionContext<any, any>, payload) {
db.getDb();
const promises = [];
const seedVersion = localStorage.getItem("SEED_VERSION") || 0;
if(payload.seed && seedVersion < SEED_VERSION ) {
console.log("SEEDING database");
promises.push(db.saveItems(seed.brands, "brands"));
promises.push(db.saveItems(seed.conditions, "conditions"));
promises.push(db.saveItems(seed.buildtype, "buildtypes"));
promises.push(db.saveItems(seed.cameras, "cameras"));
localStorage.setItem("SEED_VERSION", seed.SEED_VERSION.toString())
} else {
console.log("SEEDING skipped");
}
Promise.all(promises).then( (e) => {
console.log("All written to store")
}
)
context.dispatch("fetchCameras");
context.dispatch("fetchBrands");
context.dispatch("fetchBuildTypes");
context.dispatch("fetchShape");
context.commit("setInitialized", true);
// context.dispatch("fetchBrands");
// context.dispatch("fetchBuildTypes");
// context.dispatch("fetchCondition");
// context.commit("setInitialized", true);
}
};

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"strict": false,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,
@ -34,7 +34,8 @@
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
"tests/**/*.tsx",
"main.js"
],
"exclude": [
"node_modules"