update
This commit is contained in:
parent
b7b4e2d032
commit
997a512e00
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"spellright.language": [
|
||||||
|
"German_de_DE"
|
||||||
|
],
|
||||||
|
"spellright.documentTypes": [
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"latex",
|
||||||
|
"plaintext"
|
||||||
|
]
|
||||||
|
}
|
99
package-lock.json
generated
99
package-lock.json
generated
@ -2624,6 +2624,15 @@
|
|||||||
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
|
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"angularx-qrcode": {
|
||||||
|
"version": "10.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/angularx-qrcode/-/angularx-qrcode-10.0.11.tgz",
|
||||||
|
"integrity": "sha512-sbtqdqAboEFNoyxgG4FQYPZDzwX9TlICT2mLpsC/Se3OuT+HntW56q8E/i1BL1fJhx7zt0JJR7bc7LfofUeAlQ==",
|
||||||
|
"requires": {
|
||||||
|
"qrcode": "1.4.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ansi-colors": {
|
"ansi-colors": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
|
||||||
@ -2655,7 +2664,6 @@
|
|||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
@ -3628,7 +3636,6 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"string-width": "^3.1.0",
|
"string-width": "^3.1.0",
|
||||||
"strip-ansi": "^5.2.0",
|
"strip-ansi": "^5.2.0",
|
||||||
@ -3638,14 +3645,12 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^4.1.0"
|
"ansi-regex": "^4.1.0"
|
||||||
}
|
}
|
||||||
@ -3747,7 +3752,6 @@
|
|||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-name": "1.1.3"
|
"color-name": "1.1.3"
|
||||||
}
|
}
|
||||||
@ -3755,8 +3759,7 @@
|
|||||||
"color-name": {
|
"color-name": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"color-string": {
|
"color-string": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
@ -4472,8 +4475,7 @@
|
|||||||
"decamelize": {
|
"decamelize": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"decode-uri-component": {
|
"decode-uri-component": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
@ -4715,6 +4717,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dijkstrajs": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs="
|
||||||
|
},
|
||||||
"dir-glob": {
|
"dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
@ -4870,8 +4877,7 @@
|
|||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"emojis-list": {
|
"emojis-list": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@ -5695,7 +5701,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"locate-path": "^3.0.0"
|
"locate-path": "^3.0.0"
|
||||||
}
|
}
|
||||||
@ -5842,8 +5847,7 @@
|
|||||||
"get-caller-file": {
|
"get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"get-stream": {
|
"get-stream": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
@ -6825,8 +6829,7 @@
|
|||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-glob": {
|
"is-glob": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@ -7661,7 +7664,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-locate": "^3.0.0",
|
"p-locate": "^3.0.0",
|
||||||
"path-exists": "^3.0.0"
|
"path-exists": "^3.0.0"
|
||||||
@ -8966,7 +8968,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-try": "^2.0.0"
|
"p-try": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -8975,7 +8976,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-limit": "^2.0.0"
|
"p-limit": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -9001,8 +9001,7 @@
|
|||||||
"p-try": {
|
"p-try": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"pacote": {
|
"pacote": {
|
||||||
"version": "9.5.12",
|
"version": "9.5.12",
|
||||||
@ -9284,8 +9283,7 @@
|
|||||||
"path-exists": {
|
"path-exists": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"path-is-absolute": {
|
"path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@ -9378,6 +9376,11 @@
|
|||||||
"find-up": "^3.0.0"
|
"find-up": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pngjs": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
|
||||||
|
},
|
||||||
"pnp-webpack-plugin": {
|
"pnp-webpack-plugin": {
|
||||||
"version": "1.6.4",
|
"version": "1.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz",
|
||||||
@ -10542,6 +10545,24 @@
|
|||||||
"integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
|
"integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"qrcode": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-eR6RgxFYPDFH+zFLTJKtoNP/RlsHANQb52AUmQ2bGDPMuUw7jJb0F+DNEgx7qQGIElrbFxWYMc0/B91zLZPF9Q==",
|
||||||
|
"requires": {
|
||||||
|
"dijkstrajs": "^1.0.1",
|
||||||
|
"isarray": "^2.0.1",
|
||||||
|
"pngjs": "^3.3.0",
|
||||||
|
"yargs": "^13.2.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"qs": {
|
"qs": {
|
||||||
"version": "6.7.0",
|
"version": "6.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||||
@ -10893,14 +10914,12 @@
|
|||||||
"require-directory": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
|
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"require-main-filename": {
|
"require-main-filename": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"requires-port": {
|
"requires-port": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -11431,8 +11450,7 @@
|
|||||||
"set-blocking": {
|
"set-blocking": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
|
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"set-immediate-shim": {
|
"set-immediate-shim": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@ -12213,7 +12231,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"emoji-regex": "^7.0.1",
|
"emoji-regex": "^7.0.1",
|
||||||
"is-fullwidth-code-point": "^2.0.0",
|
"is-fullwidth-code-point": "^2.0.0",
|
||||||
@ -12223,14 +12240,12 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^4.1.0"
|
"ansi-regex": "^4.1.0"
|
||||||
}
|
}
|
||||||
@ -14117,8 +14132,7 @@
|
|||||||
"which-module": {
|
"which-module": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
|
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"worker-farm": {
|
"worker-farm": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
@ -14164,7 +14178,6 @@
|
|||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.0",
|
"ansi-styles": "^3.2.0",
|
||||||
"string-width": "^3.0.0",
|
"string-width": "^3.0.0",
|
||||||
@ -14174,14 +14187,12 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^4.1.0"
|
"ansi-regex": "^4.1.0"
|
||||||
}
|
}
|
||||||
@ -14244,8 +14255,7 @@
|
|||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@ -14257,7 +14267,6 @@
|
|||||||
"version": "13.3.2",
|
"version": "13.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||||
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"cliui": "^5.0.0",
|
"cliui": "^5.0.0",
|
||||||
"find-up": "^3.0.0",
|
"find-up": "^3.0.0",
|
||||||
@ -14275,7 +14284,6 @@
|
|||||||
"version": "13.1.2",
|
"version": "13.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"camelcase": "^5.0.0",
|
"camelcase": "^5.0.0",
|
||||||
"decamelize": "^1.2.0"
|
"decamelize": "^1.2.0"
|
||||||
@ -14284,8 +14292,7 @@
|
|||||||
"camelcase": {
|
"camelcase": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"@angular/platform-browser": "~10.1.5",
|
"@angular/platform-browser": "~10.1.5",
|
||||||
"@angular/platform-browser-dynamic": "~10.1.5",
|
"@angular/platform-browser-dynamic": "~10.1.5",
|
||||||
"@angular/router": "~10.1.5",
|
"@angular/router": "~10.1.5",
|
||||||
|
"angularx-qrcode": "^10.0.11",
|
||||||
"openpgp": "^4.10.8",
|
"openpgp": "^4.10.8",
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.0",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
|
12
src/.vscode/settings.json
vendored
Normal file
12
src/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"spellright.language": [
|
||||||
|
"English (British)",
|
||||||
|
"German_de_DE"
|
||||||
|
],
|
||||||
|
"spellright.documentTypes": [
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"latex",
|
||||||
|
"plaintext"
|
||||||
|
]
|
||||||
|
}
|
@ -2,10 +2,17 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { AuthGuard, AuthUpdateGuard, AuthenticatedGuard, AnonymousGuard } from './auth/auth.guard';
|
import { AuthGuard, AuthUpdateGuard, AuthenticatedGuard, AnonymousGuard } from './auth/auth.guard';
|
||||||
import { HomeComponent } from './pages/home/home.component';
|
import { HomeComponent, ImprintComponent, PrivacyPolicyComponent } from './pages/home/home.component';
|
||||||
|
import { HomeClubComponent } from './pages/home/club/home-club.component';
|
||||||
|
import { HomeGeneralComponent } from './pages/home/general/home-general.component';
|
||||||
|
import { HomePrivacyComponent } from './pages/home/privacy/home-privacy.component';
|
||||||
|
import { HomeServicesComponent } from './pages/home/services/home-services.component';
|
||||||
import { LoginComponent } from './pages/login/login.component';
|
import { LoginComponent } from './pages/login/login.component';
|
||||||
|
import { LoginTotpComponent } from './pages/login-totp/login-totp.component';
|
||||||
import { FormLoginComponent } from './pages/form-login/form-login.component';
|
import { FormLoginComponent } from './pages/form-login/form-login.component';
|
||||||
|
import { FormLoginTotpComponent } from './pages/form-login-totp/form-login-totp.component';
|
||||||
import { PasswordComponent } from './pages/password/password.component';
|
import { PasswordComponent } from './pages/password/password.component';
|
||||||
|
import { PasswordResetComponent } from './pages/password-reset/password-reset.component';
|
||||||
import { AccountComponent } from './pages/account/account.component';
|
import { AccountComponent } from './pages/account/account.component';
|
||||||
import { RegisterComponent } from './pages/register/register.component';
|
import { RegisterComponent } from './pages/register/register.component';
|
||||||
import { TokensComponent } from './pages/tokens/tokens.component';
|
import { TokensComponent } from './pages/tokens/tokens.component';
|
||||||
@ -17,23 +24,30 @@ import { UnavailableComponent } from './pages/unavailable/unavailable.component'
|
|||||||
import { NotfoundComponent } from './pages/notfound/notfound.component';
|
import { NotfoundComponent } from './pages/notfound/notfound.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: HomeComponent, canActivate: [AuthUpdateGuard], pathMatch: 'full', runGuardsAndResolvers: 'always' },
|
{ path: '', redirectTo: "/general", pathMatch: 'full' },
|
||||||
|
{
|
||||||
|
path: '', component: HomeComponent, canActivate: [AuthUpdateGuard], runGuardsAndResolvers: 'always', children: [
|
||||||
|
{ path: 'general', component: HomeGeneralComponent, canActivate: [AuthUpdateGuard] },
|
||||||
|
{ path: 'privacy', component: HomePrivacyComponent, canActivate: [AuthUpdateGuard] },
|
||||||
|
{ path: 'services', component: HomeServicesComponent, canActivate: [AuthUpdateGuard] },
|
||||||
|
{ path: 'club', component: HomeClubComponent, canActivate: [AuthUpdateGuard] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ path: 'imprint', component: ImprintComponent, canActivate: [AuthUpdateGuard] },
|
||||||
|
{ path: 'privacy-policy', component: PrivacyPolicyComponent, canActivate: [AuthUpdateGuard] },
|
||||||
{ path: 'login', component: LoginComponent, canActivate: [AnonymousGuard] },
|
{ path: 'login', component: LoginComponent, canActivate: [AnonymousGuard] },
|
||||||
|
{ path: 'login/totp', component: LoginTotpComponent, canActivate: [AnonymousGuard] },
|
||||||
{ path: 'external-login', component: FormLoginComponent, canActivate: [AnonymousGuard] },
|
{ path: 'external-login', component: FormLoginComponent, canActivate: [AnonymousGuard] },
|
||||||
|
{ path: 'external-login/totp', component: FormLoginTotpComponent, canActivate: [AnonymousGuard] },
|
||||||
{ path: 'password', component: PasswordComponent, canActivate: [AnonymousGuard] },
|
{ path: 'password', component: PasswordComponent, canActivate: [AnonymousGuard] },
|
||||||
|
{ path: 'password-reset', component: PasswordResetComponent, canActivate: [AnonymousGuard] },
|
||||||
{ path: 'apps', component: AppsComponent, canActivate: [AuthenticatedGuard] },
|
{ path: 'apps', component: AppsComponent, canActivate: [AuthenticatedGuard] },
|
||||||
{
|
{
|
||||||
path: 'account', component: AccountComponent, canActivate: [AuthenticatedGuard], children: [
|
path: 'account', component: AccountComponent, canActivate: [AuthenticatedGuard], children: [
|
||||||
|
|
||||||
{
|
{ path: 'info', component: InfoComponent, canActivate: [AuthenticatedGuard] },
|
||||||
path: 'info', component: InfoComponent, canActivate: [AuthenticatedGuard]
|
{ path: 'voucher', component: VoucherComponent, canActivate: [AuthenticatedGuard] },
|
||||||
},
|
{ path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard] }
|
||||||
{
|
|
||||||
path: 'voucher', component: VoucherComponent, canActivate: [AuthenticatedGuard]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard] },
|
{ path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard] },
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
<mat-toolbar color="primary">
|
<mat-toolbar color="primary">
|
||||||
<a href="javascript:" mat-icon-button aria-label="Menu">
|
<a href="javascript:" mat-icon-button>
|
||||||
<mat-icon (click)="sidenav.toggle()">menu</mat-icon>
|
<mat-icon (click)="sidenav.toggle()">menu</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
|
<mat-icon svgIcon="logo"></mat-icon>
|
||||||
<span>
|
<span>
|
||||||
<mat-icon svgIcon="logo"></mat-icon> we.bstly
|
we.bstly
|
||||||
</span>
|
</span>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<ng-container>
|
<ng-container>
|
||||||
|
|
||||||
<button mat-button [matMenuTriggerFor]="menu">
|
<button *ngIf="locales.length > 1" mat-button [matMenuTriggerFor]="menu">
|
||||||
<mat-icon>language</mat-icon>
|
<mat-icon>language</mat-icon>
|
||||||
<mat-icon>arrow_drop_down</mat-icon>
|
<mat-icon>arrow_drop_down</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #menu="matMenu">
|
<mat-menu #menu="matMenu">
|
||||||
<a mat-menu-item (click)="setLocale('en')">{{'locale.en.long' | i18n}}</a>
|
<a *ngFor="let locale of locales" mat-menu-item
|
||||||
<a mat-menu-item (click)="setLocale('de-informal')">{{'locale.de-informal.long' | i18n}}</a>
|
(click)="setLocale(locale)">{{'locale.' + locale + '.long' | i18n}}</a>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
@ -23,7 +24,7 @@
|
|||||||
<mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'side' : 'over'" [(opened)]="opened"
|
<mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'side' : 'over'" [(opened)]="opened"
|
||||||
(click)="!isBiggerScreen() && opened=false">
|
(click)="!isBiggerScreen() && opened=false">
|
||||||
<mat-nav-list>
|
<mat-nav-list>
|
||||||
<a routerLink="/" aria-label="Home" mat-list-item>
|
<a routerLink="/general" mat-list-item>
|
||||||
<mat-icon>home</mat-icon> {{'home' | i18n}}
|
<mat-icon>home</mat-icon> {{'home' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a *ngIf="!auth || auth && !auth.authenticated" routerLink="/login" routerLinkActive="active" mat-list-item>
|
<a *ngIf="!auth || auth && !auth.authenticated" routerLink="/login" routerLinkActive="active" mat-list-item>
|
||||||
@ -35,10 +36,10 @@
|
|||||||
<a *ngIf="auth && auth.authenticated" routerLink="/apps" routerLinkActive="active" mat-list-item>
|
<a *ngIf="auth && auth.authenticated" routerLink="/apps" routerLinkActive="active" mat-list-item>
|
||||||
<mat-icon>widgets</mat-icon> {{'apps' | i18n}}
|
<mat-icon>widgets</mat-icon> {{'apps' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="/tokens" aria-label="Enter tokens" mat-list-item>
|
<a routerLink="/tokens" mat-list-item>
|
||||||
<mat-icon>card_giftcard</mat-icon> {{'tokens.redeem' | i18n}}
|
<mat-icon>card_giftcard</mat-icon> {{'tokens.redeem' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a href="https://we.bstly.de" target="_blank" aria-label="Go to we.bstly.de" mat-list-item>
|
<a href="https://we.bstly.de" target="_blank" mat-list-item>
|
||||||
<mat-icon>shopping_cart</mat-icon> {{'tokens.get' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
<mat-icon>shopping_cart</mat-icon> {{'tokens.get' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
@ -46,6 +47,17 @@
|
|||||||
<mat-icon>exit_to_app</mat-icon> {{'logout' | i18n}}
|
<mat-icon>exit_to_app</mat-icon> {{'logout' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
|
|
||||||
|
<span class="spacer"></span>
|
||||||
|
|
||||||
|
<mat-nav-list>
|
||||||
|
<a routerLink="/imprint" mat-list-item style="font-size: 0.7em;">
|
||||||
|
{{'imprint' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a routerLink="/privacy-policy" mat-list-item style="font-size: 0.7em;">
|
||||||
|
{{'privacy-policy' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-nav-list>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
.spacer {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-sidenav-container {
|
|
||||||
height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-left: 15px;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
|
|
||||||
@media screen and (min-width: 576px) {
|
|
||||||
width: 540px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
width: 580px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 992px) {
|
|
||||||
width: 820px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1200px) {
|
|
||||||
width: 1000px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,11 +16,12 @@ export class AppComponent {
|
|||||||
opened = true;
|
opened = true;
|
||||||
title = 'we.bstly';
|
title = 'we.bstly';
|
||||||
currentLocale: String;
|
currentLocale: String;
|
||||||
|
locales;
|
||||||
auth;
|
auth;
|
||||||
|
|
||||||
constructor(private i18n: I18nService, private authService: AuthService, private router: Router, private iconRegistry: MatIconRegistry, private sanitizer: DomSanitizer) {
|
constructor(private i18n: I18nService, private authService: AuthService, private router: Router, private iconRegistry: MatIconRegistry, private sanitizer: DomSanitizer) {
|
||||||
this.currentLocale = this.i18n.getLocale();
|
this.currentLocale = this.i18n.getLocale();
|
||||||
|
this.locales = this.i18n.getLocales();
|
||||||
this.authService.auth.subscribe(data => {
|
this.authService.auth.subscribe(data => {
|
||||||
this.auth = data;
|
this.auth = data;
|
||||||
})
|
})
|
||||||
@ -45,7 +46,6 @@ export class AppComponent {
|
|||||||
logout() {
|
logout() {
|
||||||
this.authService.logout().subscribe(data => {
|
this.authService.logout().subscribe(data => {
|
||||||
this.router.navigate([""]);
|
this.router.navigate([""]);
|
||||||
console.log("ja?");
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,24 +6,31 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HttpClientModule, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { MaterialModule } from './material/material.module';
|
import { MaterialModule } from './material/material.module';
|
||||||
|
import { QRCodeModule } from 'angularx-qrcode';
|
||||||
|
|
||||||
import { I18nPipe } from './utils/i18n.pipe';
|
import { I18nPipe } from './utils/i18n.pipe';
|
||||||
import { HomeComponent } from './pages/home/home.component';
|
import { HomeComponent, ImprintComponent, PrivacyPolicyComponent } from './pages/home/home.component';
|
||||||
|
import { HomeClubComponent } from './pages/home/club/home-club.component';
|
||||||
|
import { HomeGeneralComponent } from './pages/home/general/home-general.component';
|
||||||
|
import { HomePrivacyComponent } from './pages/home/privacy/home-privacy.component';
|
||||||
|
import { HomeServicesComponent } from './pages/home/services/home-services.component';
|
||||||
import { AccountComponent } from './pages/account/account.component';
|
import { AccountComponent } from './pages/account/account.component';
|
||||||
import { AppsComponent } from './pages/apps/apps.component';
|
import { AppsComponent } from './pages/apps/apps.component';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { LoginComponent } from './pages/login/login.component';
|
import { LoginComponent } from './pages/login/login.component';
|
||||||
|
import { LoginTotpComponent } from './pages/login-totp/login-totp.component';
|
||||||
import { FormLoginComponent } from './pages/form-login/form-login.component';
|
import { FormLoginComponent } from './pages/form-login/form-login.component';
|
||||||
|
import { FormLoginTotpComponent } from './pages/form-login-totp/form-login-totp.component';
|
||||||
import { TokensComponent } from './pages/tokens/tokens.component';
|
import { TokensComponent } from './pages/tokens/tokens.component';
|
||||||
import { PermissionsComponent } from './ui/permissions/permissions.component';
|
import { PermissionsComponent } from './ui/permissions/permissions.component';
|
||||||
import { QuotasComponent } from './ui/quotas/quotas.component';
|
import { QuotasComponent } from './ui/quotas/quotas.component';
|
||||||
import { SecurityComponent } from './pages/account/security/security.component';
|
import { SecurityComponent, SecurityTotpDialog } from './pages/account/security/security.component';
|
||||||
import { VoucherComponent } from './pages/account/voucher/voucher.component';
|
import { VoucherComponent } from './pages/account/voucher/voucher.component';
|
||||||
import { VoucherDialog } from './pages/account/voucher/voucher.component';
|
import { VoucherDialog } from './pages/account/voucher/voucher.component';
|
||||||
import { InfoComponent } from './pages/account/info/info.component';
|
import { InfoComponent } from './pages/account/info/info.component';
|
||||||
import { PasswordComponent } from './pages/password/password.component';
|
import { PasswordComponent } from './pages/password/password.component';
|
||||||
import { RegisterComponent } from './pages/register/register.component';
|
import { PasswordResetComponent } from './pages/password-reset/password-reset.component';
|
||||||
import { RegisterDialog } from './pages/register/register.component';
|
import { RegisterComponent, RegisterDialog } from './pages/register/register.component';
|
||||||
import { UsernameDialog } from './pages/register/username-dialog/username.dialog';
|
import { UsernameDialog } from './pages/register/username-dialog/username.dialog';
|
||||||
import { UnavailableComponent } from './pages/unavailable/unavailable.component';
|
import { UnavailableComponent } from './pages/unavailable/unavailable.component';
|
||||||
import { NotfoundComponent } from './pages/notfound/notfound.component';
|
import { NotfoundComponent } from './pages/notfound/notfound.component';
|
||||||
@ -34,7 +41,7 @@ import { I18nService } from './services/i18n.service';
|
|||||||
|
|
||||||
|
|
||||||
export function init_app(i18n: I18nService) {
|
export function init_app(i18n: I18nService) {
|
||||||
return () => i18n.fetch(i18n.getLocale());
|
return () => i18n.fetch(i18n.getLocale()).then(response => { }, error => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -52,19 +59,27 @@ export class XhrInterceptor implements HttpInterceptor {
|
|||||||
declarations: [
|
declarations: [
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
HomeComponent,
|
HomeComponent, ImprintComponent, PrivacyPolicyComponent,
|
||||||
|
HomeClubComponent,
|
||||||
|
HomeGeneralComponent,
|
||||||
|
HomePrivacyComponent,
|
||||||
|
HomeServicesComponent,
|
||||||
AccountComponent,
|
AccountComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
|
LoginTotpComponent,
|
||||||
FormLoginComponent,
|
FormLoginComponent,
|
||||||
|
FormLoginTotpComponent,
|
||||||
TokensComponent,
|
TokensComponent,
|
||||||
AppsComponent,
|
AppsComponent,
|
||||||
PermissionsComponent,
|
PermissionsComponent,
|
||||||
QuotasComponent,
|
QuotasComponent,
|
||||||
SecurityComponent,
|
SecurityComponent,
|
||||||
|
SecurityTotpDialog,
|
||||||
VoucherComponent,
|
VoucherComponent,
|
||||||
VoucherDialog,
|
VoucherDialog,
|
||||||
InfoComponent,
|
InfoComponent,
|
||||||
PasswordComponent,
|
PasswordComponent,
|
||||||
|
PasswordResetComponent,
|
||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
RegisterDialog,
|
RegisterDialog,
|
||||||
UsernameDialog,
|
UsernameDialog,
|
||||||
@ -80,6 +95,7 @@ export class XhrInterceptor implements HttpInterceptor {
|
|||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
|
QRCodeModule,
|
||||||
],
|
],
|
||||||
exports: [MaterialModule],
|
exports: [MaterialModule],
|
||||||
providers: [{ provide: APP_INITIALIZER, useFactory: init_app, deps: [I18nService], multi: true }, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true }],
|
providers: [{ provide: APP_INITIALIZER, useFactory: init_app, deps: [I18nService], multi: true }, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true }],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h2>{{'greet' | i18n:auth.name}} <mat-icon aria-hidden="false" aria-label="Smile">sentiment_satisfied_alt</mat-icon>
|
<h2>{{'greet' | i18n:auth.name}} <mat-icon>sentiment_satisfied_alt</mat-icon>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<nav mat-tab-nav-bar>
|
<nav mat-tab-nav-bar>
|
||||||
|
16
src/app/pages/account/security/security-totp.dialog.html
Normal file
16
src/app/pages/account/security/security-totp.dialog.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<h1 mat-dialog-title>{{'security.2fa.totp.enable' | i18n}}</h1>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
|
||||||
|
{{'security.2fa.totp.hint' | i18n}}
|
||||||
|
|
||||||
|
<qrcode *ngIf="data.qrData" [qrdata]="data.qrData" [width]="400" [errorCorrectionLevel]="'M'" title="{{data.qrData}}"></qrcode>
|
||||||
|
|
||||||
|
{{'security.2fa.totp.activate' | i18n}}
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="{{'security.2fa.totp.code' | i18n}}" [formControl]="code" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
|
||||||
|
<button [disabled]="code.invalid" mat-raised-button [mat-dialog-close]="code.value" cdkFocusInitial color="accent">{{'security.2fa.totp.enable' | i18n}}</button>
|
||||||
|
</div>
|
@ -1,4 +1,4 @@
|
|||||||
<form [formGroup]="form" (ngSubmit)="changePassword()" #formDirective="ngForm">
|
<form [formGroup]="passwordForm" (ngSubmit)="changePassword()" #passwordFormDirective="ngForm">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<h2>{{'password.change' | i18n}}</h2>
|
<h2>{{'password.change' | i18n}}</h2>
|
||||||
@ -6,22 +6,22 @@
|
|||||||
{{'password.changed' | i18n}}
|
{{'password.changed' | i18n}}
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput type="password" placeholder="{{'password.current' | i18n}}" formControlName="oldPassword"
|
<input matInput type="password" placeholder="{{'password.current' | i18n}}"
|
||||||
[(ngModel)]="model.old">
|
formControlName="oldPassword" [(ngModel)]="model.old">
|
||||||
<mat-error *ngFor="let error of form.get('oldPassword').errors | keyvalue">
|
<mat-error *ngFor="let error of passwordForm.get('oldPassword').errors | keyvalue">
|
||||||
{{error.key}}
|
{{error.key}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput type="password" placeholder="{{'password' | i18n}}" formControlName="password"
|
<input matInput type="password" placeholder="{{'password' | i18n}}" formControlName="password"
|
||||||
[(ngModel)]="model.password">
|
[(ngModel)]="model.password">
|
||||||
<mat-error *ngFor="let error of form.get('password').errors | keyvalue">
|
<mat-error *ngFor="let error of passwordForm.get('password').errors | keyvalue">
|
||||||
{{error.key}}
|
{{error.key}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput type="password" placeholder="{{'password.confirm' | i18n}}" formControlName="password2"
|
<input matInput type="password" length="6" placeholder="{{'password.confirm' | i18n}}"
|
||||||
[(ngModel)]="model.password2">
|
formControlName="password2" [(ngModel)]="model.password2">
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{'password.not-match' | i18n}}
|
{{'password.not-match' | i18n}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
@ -29,9 +29,20 @@
|
|||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
|
||||||
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="passwordForm.invalid">
|
||||||
{{'password.change' | i18n}}
|
{{'password.change' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<h2>{{'security.2fa' | i18n}}</h2>
|
||||||
|
<p>{{'security.2fa.info' | i18n}}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button *ngIf="!totp" (click)="createTotp()" mat-raised-button color="accent">{{'security.2fa.totp.create' | i18n}}</button>
|
||||||
|
<button *ngIf="totp" (click)="removeTotp()" mat-raised-button color="warn">{{'security.2fa.totp.remove' | i18n}}</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
@ -1,3 +1,3 @@
|
|||||||
mat-form-field {
|
mat-form-field {
|
||||||
display: block;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild, Inject } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
|
import { FormBuilder, FormGroup, FormControl, Validators, NgForm } from '@angular/forms';
|
||||||
|
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
import { AuthService } from './../../../services/auth.service';
|
||||||
import { UserService } from './../../../services/user.service';
|
import { UserService } from './../../../services/user.service';
|
||||||
import { MatchingValidator } from './../../../utils/matching.validator';
|
import { MatchingValidator } from './../../../utils/matching.validator';
|
||||||
|
|
||||||
@ -15,28 +17,39 @@ export class SecurityComponent implements OnInit {
|
|||||||
model: any = {};
|
model: any = {};
|
||||||
public working: boolean;
|
public working: boolean;
|
||||||
public success: boolean;
|
public success: boolean;
|
||||||
form: FormGroup;
|
public totp: boolean = false;
|
||||||
@ViewChild('formDirective') private formDirective: NgForm;
|
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder, private userService: UserService) { }
|
passwordForm: FormGroup;
|
||||||
|
@ViewChild('passwordFormDirective') private passwordFormDirective: NgForm;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private userService: UserService,
|
||||||
|
private authService: AuthService,
|
||||||
|
public dialog: MatDialog) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.form = this.formBuilder.group({
|
this.passwordForm = this.formBuilder.group({
|
||||||
oldPassword: ['', Validators.required],
|
oldPassword: ['', Validators.required],
|
||||||
password: ['', Validators.required],
|
password: ['', Validators.required],
|
||||||
password2: ['', Validators.required]
|
password2: ['', Validators.required]
|
||||||
}, {
|
}, {
|
||||||
validator: MatchingValidator('password', 'password2')
|
validator: MatchingValidator('password', 'password2')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.authService.isTotpEnabled().subscribe(response => {
|
||||||
|
this.totp = true;
|
||||||
|
}, error => {
|
||||||
|
this.totp = false;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
changePassword() {
|
changePassword() {
|
||||||
if (this.form.valid && !this.working) {
|
if (this.passwordForm.valid && !this.working) {
|
||||||
this.working = true;
|
this.working = true;
|
||||||
|
|
||||||
this.userService.password(this.model).subscribe((result: any) => {
|
this.userService.password(this.model).subscribe((result: any) => {
|
||||||
this.formDirective.resetForm();
|
this.passwordFormDirective.resetForm();
|
||||||
this.success = true;
|
this.success = true;
|
||||||
this.working = false;
|
this.working = false;
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
@ -49,11 +62,68 @@ export class SecurityComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let code in errors) {
|
for (let code in errors) {
|
||||||
this.form.get(code).setErrors(errors[code]);
|
this.passwordForm.get(code).setErrors(errors[code]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTotp() {
|
||||||
|
this.authService.createTotp().subscribe((result: any) => {
|
||||||
|
const dialogRef = this.dialog.open(SecurityTotpDialog, {
|
||||||
|
closeOnNavigation: false,
|
||||||
|
disableClose: true,
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.authService.enableTotp(result).subscribe((result: any) => {
|
||||||
|
this.totp = true;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.authService.removeTotp().subscribe((result: any) => {
|
||||||
|
this.totp = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enableTotp() {
|
||||||
|
const dialogRef = this.dialog.open(SecurityTotpDialog, {
|
||||||
|
closeOnNavigation: false,
|
||||||
|
disableClose: true,
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTotp() {
|
||||||
|
this.authService.removeTotp().subscribe((result: any) => {
|
||||||
|
this.totp = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-security-totp-dialog',
|
||||||
|
templateUrl: 'security-totp.dialog.html',
|
||||||
|
styleUrls: ['./security.component.scss']
|
||||||
|
})
|
||||||
|
export class SecurityTotpDialog {
|
||||||
|
|
||||||
|
code: FormControl;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<SecurityTotpDialog>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.code = new FormControl('', [Validators.required, Validators.pattern("[0-9]{6}")]);
|
||||||
|
}
|
||||||
}
|
}
|
@ -57,7 +57,6 @@ export class VoucherComponent implements OnInit {
|
|||||||
data: this.model
|
data: this.model
|
||||||
});
|
});
|
||||||
}, error => {
|
}, error => {
|
||||||
console.log(error);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
<h3>{{'apps' | i18n}}</h3>
|
<h3>{{'apps' | i18n}}</h3>
|
||||||
<mat-grid-list cols="2">
|
|
||||||
<mat-grid-tile *ngFor="let app of apps">
|
<mat-card *ngFor="let app of apps">
|
||||||
<mat-card>
|
<mat-card-header>
|
||||||
<mat-card-header>
|
<mat-icon>{{'apps.' + app.name + '.icon' | i18n}}</mat-icon>
|
||||||
<mat-icon>{{'app.' + app.name + '.icon' | i18n}}</mat-icon>
|
<mat-card-title>{{'apps.' + app.name + '.title' | i18n}}</mat-card-title>
|
||||||
<mat-card-title>{{'app.' + app.name + '.title' | i18n}}</mat-card-title>
|
<mat-card-subtitle>{{'apps.' + app.name + '.subtitle' | i18n}}</mat-card-subtitle>
|
||||||
<mat-card-subtitle>{{'app.' + app.name + '.subtitle' | i18n}}</mat-card-subtitle>
|
</mat-card-header>
|
||||||
</mat-card-header>
|
<mat-card-content>
|
||||||
<mat-card-content>
|
<p>
|
||||||
<p>
|
{{ 'apps.' + app.name + '.text' | i18n}}
|
||||||
{{ 'app.' + app.name + '.text' | i18n}}
|
</p>
|
||||||
</p>
|
</mat-card-content>
|
||||||
</mat-card-content>
|
<mat-card-actions>
|
||||||
<mat-card-actions>
|
<a href="{{app.url}}" target="_blank" mat-raised-button color="primary">{{'apps.goto' | i18n}}</a>
|
||||||
<a href="{{app.url}}" target="_blank" mat-raised-button color="primary">{{'app.goto' | i18n}}</a>
|
</mat-card-actions>
|
||||||
</mat-card-actions>
|
</mat-card>
|
||||||
</mat-card>
|
|
||||||
</mat-grid-tile>
|
|
||||||
</mat-grid-list>
|
|
@ -1,4 +0,0 @@
|
|||||||
mat-card {
|
|
||||||
width: 100%;
|
|
||||||
margin: 2em 2em;
|
|
||||||
}
|
|
28
src/app/pages/form-login-totp/form-login-totp.component.html
Normal file
28
src/app/pages/form-login-totp/form-login-totp.component.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<form [formGroup]="form">
|
||||||
|
<form ngNoForm action="{{apiUrl}}/auth/formlogin/totp" method="POST">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<h2>{{'security.2fa.totp.external' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
||||||
|
</mat-icon></h2>
|
||||||
|
<mat-error *ngIf="loginInvalid">
|
||||||
|
{{'security.2fa.totp.invalid' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
<mat-form-field>
|
||||||
|
<input id="code" name="code" matInput placeholder="{{'security.2fa.totp.code' | i18n}}"
|
||||||
|
formControlName="code" required>
|
||||||
|
<mat-error>
|
||||||
|
{{'security.2fa.totp.missing' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-slide-toggle id="keep" name="keep" formControlName="keep">
|
||||||
|
{{'security.2fa.totp.keepSession' | i18n}}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button type="submit" mat-raised-button color="primary"
|
||||||
|
[disabled]="form.invalid">{{'security.2fa.totp.login' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
||||||
|
</mat-icon></button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</form>
|
||||||
|
</form>
|
@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FormLoginTotpComponent } from './form-login-totp.component';
|
||||||
|
|
||||||
|
describe('FormLoginTotpComponent', () => {
|
||||||
|
let component: FormLoginTotpComponent;
|
||||||
|
let fixture: ComponentFixture<FormLoginTotpComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ FormLoginTotpComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FormLoginTotpComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
28
src/app/pages/form-login-totp/form-login-totp.component.ts
Normal file
28
src/app/pages/form-login-totp/form-login-totp.component.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-form-login-totp',
|
||||||
|
templateUrl: './form-login-totp.component.html',
|
||||||
|
styleUrls: ['./form-login-totp.component.scss']
|
||||||
|
})
|
||||||
|
export class FormLoginTotpComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
public loginInvalid: boolean;
|
||||||
|
public apiUrl = environment.apiUrl;
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) { }
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
code: ['', Validators.required],
|
||||||
|
keep : ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
<form [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
<form ngNoForm action="{{apiUrl}}/auth/login" method="POST">
|
<form ngNoForm action="{{apiUrl}}/auth/formlogin" method="POST">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<h2>{{'login.external' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
<h2>{{'login.external' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
||||||
</mat-icon></h2>
|
</mat-icon>
|
||||||
|
</h2>
|
||||||
<mat-error *ngIf="loginInvalid">
|
<mat-error *ngIf="loginInvalid">
|
||||||
{{'login.invalid' | i18n}}
|
{{'login.invalid' | i18n}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
@ -21,13 +22,15 @@
|
|||||||
{{'password.invalid.hint' | i18n}}
|
{{'password.invalid.hint' | i18n}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-slide-toggle id="keep" name="keep" formControlName="keep">
|
||||||
|
{{'login.keepSession' | i18n}}
|
||||||
|
</mat-slide-toggle>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button type="submit" mat-raised-button color="primary"
|
<button type="submit" mat-raised-button color="primary"
|
||||||
[disabled]="form.invalid">{{'login.external' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
[disabled]="form.invalid">{{'login.external' | i18n}}<mat-icon style="font-size: 1em;">open_in_new
|
||||||
</mat-icon></button>
|
</mat-icon></button>
|
||||||
<a routerLink="/password" aria-label="Enter tokens" mat-raised-button
|
<a routerLink="/password" mat-raised-button color="warn">{{'password.forgot' | i18n}}</a>
|
||||||
color="warn">{{'password.forgot' | i18n}}</a>
|
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</form>
|
</form>
|
||||||
|
@ -19,7 +19,8 @@ export class FormLoginComponent implements OnInit {
|
|||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.form = this.formBuilder.group({
|
this.form = this.formBuilder.group({
|
||||||
username: ['', Validators.required],
|
username: ['', Validators.required],
|
||||||
password: ['', Validators.required]
|
password: ['', Validators.required],
|
||||||
|
keep : ['']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
src/app/pages/home/club/home-club.component.html
Normal file
29
src/app/pages/home/club/home-club.component.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel expanded>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.club.membership' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'club/membership'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.club.about' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'club/about'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.club.charter' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'club/charter'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
</mat-accordion>
|
25
src/app/pages/home/club/home-club.component.spec.ts
Normal file
25
src/app/pages/home/club/home-club.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomeClubComponent } from './home-club.component';
|
||||||
|
|
||||||
|
describe('HomeClubComponent', () => {
|
||||||
|
let component: HomeClubComponent;
|
||||||
|
let fixture: ComponentFixture<HomeClubComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HomeClubComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HomeClubComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
16
src/app/pages/home/club/home-club.component.ts
Normal file
16
src/app/pages/home/club/home-club.component.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home-club',
|
||||||
|
templateUrl: './home-club.component.html'
|
||||||
|
})
|
||||||
|
export class HomeClubComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/app/pages/home/general/home-general.component.html
Normal file
28
src/app/pages/home/general/home-general.component.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel expanded>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.general.what' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'general/what'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.general.you' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'general/you'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.general.we' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'general/we'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
25
src/app/pages/home/general/home-general.component.spec.ts
Normal file
25
src/app/pages/home/general/home-general.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomeGeneralComponent } from './home-general.component';
|
||||||
|
|
||||||
|
describe('HomeGeneralComponent', () => {
|
||||||
|
let component: HomeGeneralComponent;
|
||||||
|
let fixture: ComponentFixture<HomeGeneralComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HomeGeneralComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HomeGeneralComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
16
src/app/pages/home/general/home-general.component.ts
Normal file
16
src/app/pages/home/general/home-general.component.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home-general',
|
||||||
|
templateUrl: './home-general.component.html'
|
||||||
|
})
|
||||||
|
export class HomeGeneralComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,28 +1,15 @@
|
|||||||
<app-html [template]="'about'"></app-html>
|
<app-html [template]="'about'"></app-html>
|
||||||
|
|
||||||
<h3>F.A.Q. - Frequently Asked Questions</h3>
|
<nav mat-tab-nav-bar>
|
||||||
<mat-accordion>
|
<a mat-tab-link routerLink="general" routerLinkActive #rlag="routerLinkActive"
|
||||||
<mat-expansion-panel>
|
[active]="rlag.isActive">{{'home.general' | i18n}}</a>
|
||||||
<mat-expansion-panel-header>
|
<a mat-tab-link routerLink="services" routerLinkActive #rlas="routerLinkActive"
|
||||||
<mat-panel-title>
|
[active]="rlas.isActive">{{'home.services' | i18n}}</a>
|
||||||
Question 1
|
<a mat-tab-link routerLink="privacy" routerLinkActive #rlap="routerLinkActive"
|
||||||
</mat-panel-title>
|
[active]="rlap.isActive">{{'home.privacy' | i18n}}</a>
|
||||||
<mat-panel-description>
|
<a mat-tab-link routerLink="club" routerLinkActive #rlac="routerLinkActive"
|
||||||
Answers 1 summaray
|
[active]="rlac.isActive">{{'home.club' | i18n}}</a>
|
||||||
</mat-panel-description>
|
</nav>
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<p>TAnswer 1 detail</p>
|
|
||||||
</mat-expansion-panel>
|
|
||||||
|
|
||||||
<mat-expansion-panel>
|
<p></p>
|
||||||
<mat-expansion-panel-header>
|
<router-outlet></router-outlet>
|
||||||
<mat-panel-title>
|
|
||||||
Question 2
|
|
||||||
</mat-panel-title>
|
|
||||||
<mat-panel-description>
|
|
||||||
Answers 2 summaray
|
|
||||||
</mat-panel-description>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<p>TAnswer 2 detail</p>
|
|
||||||
</mat-expansion-panel>
|
|
||||||
</mat-accordion>
|
|
@ -1,6 +1,5 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
@ -15,3 +14,31 @@ export class HomeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-imprint',
|
||||||
|
templateUrl: './home.imprint.html'
|
||||||
|
})
|
||||||
|
export class ImprintComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-privacy-policy',
|
||||||
|
templateUrl: './home.privacy-policy.html'
|
||||||
|
})
|
||||||
|
export class PrivacyPolicyComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
src/app/pages/home/home.imprint.html
Normal file
1
src/app/pages/home/home.imprint.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<app-html [template]="'imprint'"></app-html>
|
1
src/app/pages/home/home.privacy-policy.html
Normal file
1
src/app/pages/home/home.privacy-policy.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<app-html [template]="'privacy-policy'"></app-html>
|
57
src/app/pages/home/privacy/home-privacy.component.html
Normal file
57
src/app/pages/home/privacy/home-privacy.component.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<h3> {{'home.privacy.design' | i18n}}</h3>
|
||||||
|
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel expanded hideToggle disabled>
|
||||||
|
<app-html [template]="'privacy/design'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
|
||||||
|
<h3>{{'home.privacy.services' | i18n}}</h3>
|
||||||
|
|
||||||
|
<mat-accordion>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Webserver
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'privacy/webserver'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Pretix
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'privacy/pretix'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
we.bstly
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'privacy/we-bstly'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Nextcloud
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'privacy/nextcloud'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.services.email' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'privacy/email'"></app-html>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
25
src/app/pages/home/privacy/home-privacy.component.spec.ts
Normal file
25
src/app/pages/home/privacy/home-privacy.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomePrivacyComponent } from './home-privacy.component';
|
||||||
|
|
||||||
|
describe('HomePrivacyComponent', () => {
|
||||||
|
let component: HomePrivacyComponent;
|
||||||
|
let fixture: ComponentFixture<HomePrivacyComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HomePrivacyComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HomePrivacyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
16
src/app/pages/home/privacy/home-privacy.component.ts
Normal file
16
src/app/pages/home/privacy/home-privacy.component.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home-privacy',
|
||||||
|
templateUrl: './home-privacy.component.html'
|
||||||
|
})
|
||||||
|
export class HomePrivacyComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
523
src/app/pages/home/services/home-services.component.html
Normal file
523
src/app/pages/home/services/home-services.component.html
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
<h3>{{'home.services.active' | i18n}}</h3>
|
||||||
|
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel expanded>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
we.bstly
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/active/we-bstly'"></app-html>
|
||||||
|
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="webstly">we.bstly</mat-chip>
|
||||||
|
<mat-menu #webstly="matMenu">
|
||||||
|
<a href="https://git.bstly.de/_Bastler/we.bstly" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://www.bstly.de" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Nextcloud
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/active/nextcloud'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="nextcloud">Nextcloud</mat-chip>
|
||||||
|
<mat-menu #nextcloud="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/server" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://nextcloud.com" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="nextcloud_collabora">Collabora Online</mat-chip>
|
||||||
|
<mat-menu #nextcloud_collabora="matMenu">
|
||||||
|
<a href="https://github.com/CollaboraOnline/online" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://www.collaboraoffice.com/code/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
<br>
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Apps
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<mat-panel-description>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip>Accessibility</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_announcementcenter">Announcement center
|
||||||
|
</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_announcementcenter="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/announcementcenter" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_apporder">AppOrder</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_apporder="matMenu">
|
||||||
|
<a href="https://github.com/juliushaertl/apporder" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_calendar">Calendar</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_calendar="matMenu">
|
||||||
|
<a href="https://apps.nextcloud.com/apps/calendar" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_checksum">Checksum</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_checksum="matMenu">
|
||||||
|
<a href="https://github.com/westberliner/owncloud-checksum/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_collabora">Collabora Online</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_collabora="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/richdocuments" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip>Collaborative tags</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip>Comments</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_contacts">Contacts</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_contacts="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/contacts" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_contacts">Contacts</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_contacts="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/contacts" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_custom_menu">Custom menu</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_custom_menu="matMenu">
|
||||||
|
<a href="https://gitnet.fr/deblan/side_menu" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_data_request">Data Request</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_data_request="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/data_request" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_deck">Deck</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_deck="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/deck" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip>Default encryption module</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip>Deleted files</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_external_sites">External sites</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_external_sites="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/external" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip>Federation</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip>File sharing</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip>First run wizard</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_notifications">Notifications</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_notifications="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/notifications" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_oidc">OpenID Connect Login</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_oidc="matMenu">
|
||||||
|
<a href="https://github.com/pulsejet/nextcloud-single-openid-connect" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_password_policy">Password policy
|
||||||
|
</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_password_policy="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/password_policy" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_pdf">PDF viewer</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_pdf="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/files_pdfviewer" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_photos">Photos</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_photos="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/photos" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_polls">Polls</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_polls="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/polls" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_privacy">Privacy</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_privacy="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/privacy" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_quota">Quota warning</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_quota="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/quota_warning" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_rightclick">Right click</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_rightclick="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/files_rightclick" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip>Share by mail</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip>Support</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_talk">Talk</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_talk="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/spreed" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_tasks">Tasks</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_tasks="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/tasks/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_terms">Terms of services</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_terms="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/terms_of_service/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_text">Text</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_text="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/text" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip>Theming</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_2fagateway">Two-Factor Gateway</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_2fagateway="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/twofactor_gateway" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_2fatotp">Two-Factor TOTP Provider
|
||||||
|
</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_2fatotp="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/twofactor_totp" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip>Versions</mat-chip>
|
||||||
|
|
||||||
|
<mat-chip selected [matMenuTriggerFor]="nextcloud_app_videoplayer">Video player</mat-chip>
|
||||||
|
<mat-menu #nextcloud_app_videoplayer="matMenu">
|
||||||
|
<a href="https://github.com/nextcloud/files_videoplayer" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
|
||||||
|
</mat-panel-description>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'home.services.email' | i18n}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/active/email'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="dovecot">Dovecot</mat-chip>
|
||||||
|
<mat-menu #dovecot="matMenu">
|
||||||
|
<a href="https://github.com/dovecot/core" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://www.dovecot.org" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="postfix">Postfix</mat-chip>
|
||||||
|
<mat-menu #postfix="matMenu">
|
||||||
|
<a href="http://www.postfix.org/download.html" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="http://www.postfix.org" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="rspamd">Rspamd</mat-chip>
|
||||||
|
<mat-menu #rspamd="matMenu">
|
||||||
|
<a href="https://github.com/rspamd/rspamd" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://www.rspamd.com" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="rainloop">RainLoop Webmail</mat-chip>
|
||||||
|
<mat-menu #rainloop="matMenu">
|
||||||
|
<a href="https://github.com/RainLoop/rainloop-webmail" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://www.rainloop.net" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
|
||||||
|
<h3>{{'home.services.planned' | i18n}}</h3>
|
||||||
|
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Matrix
|
||||||
|
✅
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/matrix'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="synapse">Synapse</mat-chip>
|
||||||
|
<mat-menu #synapse="matMenu">
|
||||||
|
<a href="https://github.com/matrix-org/synapse/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://matrix.org/docs/projects/server/synapse" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="element-web">Element Web</mat-chip>
|
||||||
|
<mat-menu #element-web="matMenu">
|
||||||
|
<a href="https://github.com/vector-im/element-web" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://element.io/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Gitea
|
||||||
|
✅
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/gitea'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="gitea">Gitea</mat-chip>
|
||||||
|
<mat-menu #gitea="matMenu">
|
||||||
|
<a href="https://github.com/go-gitea/gitea" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://gitea.io/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Jitsi Meet
|
||||||
|
❔
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/jitsi-meet'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="jitsi">Jitsi Meet</mat-chip>
|
||||||
|
<mat-menu #jitsi="matMenu">
|
||||||
|
<a href="https://github.com/jitsi/jitsi-meet" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://jitsi.org/jitsi-meet/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Wireguard
|
||||||
|
⚠️
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/wireguard'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="wireguard">Wireguard</mat-chip>
|
||||||
|
<mat-menu #wireguard="matMenu">
|
||||||
|
<a href="https://www.wireguard.com/repositories/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://www.wireguard.com" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
PiHole
|
||||||
|
❔
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/pihole'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="pihole">Pi-Hole</mat-chip>
|
||||||
|
<mat-menu #pihole="matMenu">
|
||||||
|
<a href="https://github.com/pi-hole/pi-hole" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://pi-hole.net" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
BigBlueButton
|
||||||
|
⚠️
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/bigbluebutton'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="bigbluebutton">BigBlueButton</mat-chip>
|
||||||
|
<mat-menu #bigbluebutton="matMenu">
|
||||||
|
<a href="https://github.com/bigbluebutton/bigbluebutton" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://bigbluebutton.org" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Bitwarden
|
||||||
|
⚠️
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-html [template]="'services/planned/bitwarden'"></app-html>
|
||||||
|
|
||||||
|
<span>{{'software' | i18n}}:</span>
|
||||||
|
<mat-chip-list>
|
||||||
|
<mat-chip color="accent" selected [matMenuTriggerFor]="bitwarden">Bitwarden</mat-chip>
|
||||||
|
<mat-menu #bitwarden="matMenu">
|
||||||
|
<a href="https://github.com/bitwarden/server" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>code</mat-icon> {{'sourcecode' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="https://bitwarden.com/" target="_blank" mat-menu-item>
|
||||||
|
<mat-icon>public</mat-icon> {{'homepage' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
|
||||||
|
<p>{{'home.services.legend' | i18n}}
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item>{{'home.services.legend.ready' | i18n}}</mat-list-item>
|
||||||
|
<mat-list-item>{{'home.services.legend.not-ready' | i18n}}</mat-list-item>
|
||||||
|
<mat-list-item>{{'home.services.legend.not-available' | i18n}}</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</p>
|
25
src/app/pages/home/services/home-services.component.spec.ts
Normal file
25
src/app/pages/home/services/home-services.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomeServicesComponent } from './home-services.component';
|
||||||
|
|
||||||
|
describe('HomeServicesComponent', () => {
|
||||||
|
let component: HomeServicesComponent;
|
||||||
|
let fixture: ComponentFixture<HomeServicesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HomeServicesComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HomeServicesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
16
src/app/pages/home/services/home-services.component.ts
Normal file
16
src/app/pages/home/services/home-services.component.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home-services',
|
||||||
|
templateUrl: './home-services.component.html'
|
||||||
|
})
|
||||||
|
export class HomeServicesComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
src/app/pages/login-totp/login-totp.component.html
Normal file
24
src/app/pages/login-totp/login-totp.component.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<form [formGroup]="form" (ngSubmit)="loginTotp()">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<h2>{{'security.2fa.totp' | i18n}}</h2>
|
||||||
|
<mat-error *ngIf="loginInvalid">
|
||||||
|
{{'security.2fa.totp.invalid' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
<mat-form-field>
|
||||||
|
<input id="code" name="code" matInput placeholder="{{'security.2fa.totp.code' | i18n}}" formControlName="code"
|
||||||
|
required>
|
||||||
|
<mat-error>
|
||||||
|
{{'security.2fa.totp.missing' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-slide-toggle id="keep" name="keep" formControlName="keep">
|
||||||
|
{{'security.2fa.totp.keepSession' | i18n}}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button type="submit" mat-raised-button color="primary"
|
||||||
|
[disabled]="form.invalid">{{'security.2fa.totp.login' | i18n}}</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</form>
|
3
src/app/pages/login-totp/login-totp.component.scss
Normal file
3
src/app/pages/login-totp/login-totp.component.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
25
src/app/pages/login-totp/login-totp.component.spec.ts
Normal file
25
src/app/pages/login-totp/login-totp.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LoginTotpComponent } from './login-totp.component';
|
||||||
|
|
||||||
|
describe('LoginTotpComponent', () => {
|
||||||
|
let component: LoginTotpComponent;
|
||||||
|
let fixture: ComponentFixture<LoginTotpComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ LoginTotpComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(LoginTotpComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
54
src/app/pages/login-totp/login-totp.component.ts
Normal file
54
src/app/pages/login-totp/login-totp.component.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login-totp.component.html',
|
||||||
|
styleUrls: ['./login-totp.component.scss']
|
||||||
|
})
|
||||||
|
export class LoginTotpComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
public loginInvalid: boolean;
|
||||||
|
public apiUrl = environment.apiUrl;
|
||||||
|
targetRoute = '/account/info';
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder, private authService: AuthService, private router: Router, private route: ActivatedRoute) { }
|
||||||
|
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
code: ['', Validators.required],
|
||||||
|
keep: ['']
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params['target']) {
|
||||||
|
this.targetRoute = params['target'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginTotp() {
|
||||||
|
this.loginInvalid = false;
|
||||||
|
if (this.form.valid) {
|
||||||
|
|
||||||
|
const totpModel = {
|
||||||
|
code: this.form.get('code').value,
|
||||||
|
keep: this.form.get('keep').value
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.loginTotp(totpModel).subscribe((response: any) => {
|
||||||
|
this.router.navigate([this.targetRoute]);
|
||||||
|
}, error => {
|
||||||
|
this.loginInvalid = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,12 +19,14 @@
|
|||||||
{{'password.invalid.hint' | i18n}}
|
{{'password.invalid.hint' | i18n}}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-slide-toggle id="keep" name="keep" formControlName="keep">
|
||||||
|
{{'login.keepSession' | i18n}}
|
||||||
|
</mat-slide-toggle>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button type="submit" mat-raised-button color="primary"
|
<button type="submit" mat-raised-button color="primary"
|
||||||
[disabled]="form.invalid">{{'login' | i18n}}</button>
|
[disabled]="form.invalid">{{'login' | i18n}}</button>
|
||||||
<a routerLink="/password" aria-label="Enter tokens" mat-raised-button
|
<a routerLink="/password" mat-raised-button color="warn">{{'password.forgot' | i18n}}</a>
|
||||||
color="warn">{{'password.forgot' | i18n}}</a>
|
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</form>
|
</form>
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { AuthService } from './../../services/auth.service';
|
import { AuthService } from './../../services/auth.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { environment } from './../../../environments/environment';
|
import { environment } from './../../../environments/environment';
|
||||||
|
|
||||||
@ -15,27 +15,40 @@ export class LoginComponent implements OnInit {
|
|||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
public loginInvalid: boolean;
|
public loginInvalid: boolean;
|
||||||
public apiUrl = environment.apiUrl;
|
public apiUrl = environment.apiUrl;
|
||||||
|
targetRoute = '/account/info';
|
||||||
loginModel = {};
|
loginModel = {};
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder, private authService: AuthService, private router: Router) { }
|
constructor(private formBuilder: FormBuilder, private authService: AuthService, private router: Router, private route: ActivatedRoute) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.form = this.formBuilder.group({
|
this.form = this.formBuilder.group({
|
||||||
username: ['', Validators.required],
|
username: ['', Validators.required],
|
||||||
password: ['', Validators.required]
|
password: ['', Validators.required],
|
||||||
|
keep: ['']
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params['target']) {
|
||||||
|
this.targetRoute = params['target'];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async login() {
|
async login() {
|
||||||
this.loginInvalid = false;
|
this.loginInvalid = false;
|
||||||
if (this.form.valid) {
|
if (this.form.valid) {
|
||||||
const username = this.form.get('username').value;
|
|
||||||
const password = this.form.get('password').value;
|
const loginModel = {
|
||||||
this.authService.login(username, password).subscribe((response: any) => {
|
username: this.form.get('username').value,
|
||||||
this.router.navigate(["/account/info"]);
|
password: this.form.get('password').value,
|
||||||
|
keep: this.form.get('keep').value
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.login(loginModel).subscribe((response: any) => {
|
||||||
|
this.router.navigate([this.targetRoute]);
|
||||||
}, error => {
|
}, error => {
|
||||||
if (error.status == 302) {
|
if (error.status == 428) {
|
||||||
console.log(error);
|
this.router.navigate(["/login/totp"], { queryParams: { target: this.targetRoute } });
|
||||||
} else {
|
} else {
|
||||||
this.loginInvalid = true;
|
this.loginInvalid = true;
|
||||||
}
|
}
|
||||||
|
43
src/app/pages/password-reset/password-reset.component.html
Normal file
43
src/app/pages/password-reset/password-reset.component.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<form [formGroup]="form" (ngSubmit)="passwordReset()" *ngIf="!success">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<h2>{{'password.reset' | i18n}}</h2>
|
||||||
|
<mat-error *ngIf="tokenInvalid">
|
||||||
|
{{'password.reset.tokenInvalid' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="password" placeholder="{{'password' | i18n}}" formControlName="password"
|
||||||
|
[(ngModel)]="model.password">
|
||||||
|
<mat-error *ngFor="let error of form.get('password').errors | keyvalue">
|
||||||
|
{{error.key}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="password" length="6" placeholder="{{'password.confirm' | i18n}}"
|
||||||
|
formControlName="password2" [(ngModel)]="model.password2">
|
||||||
|
<mat-error>
|
||||||
|
{{'password.not-match' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
||||||
|
{{'password.reset' | i18n}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<mat-card *ngIf="success">
|
||||||
|
<mat-card-content>
|
||||||
|
<h2>{{'password.reset.success.title' | i18n}}</h2>
|
||||||
|
<p>{{'password.reset.success.text' | i18n}}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<a routerLink="/login" mat-raised-button color="primary">
|
||||||
|
{{'password.reset.login' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PasswordResetComponent } from './password-reset.component';
|
||||||
|
|
||||||
|
describe('PasswordResetComponent', () => {
|
||||||
|
let component: PasswordResetComponent;
|
||||||
|
let fixture: ComponentFixture<PasswordResetComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ PasswordResetComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PasswordResetComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
65
src/app/pages/password-reset/password-reset.component.ts
Normal file
65
src/app/pages/password-reset/password-reset.component.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { MatchingValidator } from '../../utils/matching.validator';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-password-reset',
|
||||||
|
templateUrl: './password-reset.component.html',
|
||||||
|
styleUrls: ['./password-reset.component.scss']
|
||||||
|
})
|
||||||
|
export class PasswordResetComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
model: any = {};
|
||||||
|
public working: boolean;
|
||||||
|
public success: boolean;
|
||||||
|
public tokenInvalid: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
password: ['', Validators.required],
|
||||||
|
password2: ['', Validators.required]
|
||||||
|
}, {
|
||||||
|
validator: MatchingValidator('password', 'password2')
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params.token) {
|
||||||
|
this.model.token = params.token;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordReset() {
|
||||||
|
this.working = true;
|
||||||
|
this.authService.passwordReset(this.model).subscribe(response => {
|
||||||
|
this.success = true;
|
||||||
|
}, (error) => {
|
||||||
|
this.working = false;
|
||||||
|
if (error.status == 409) {
|
||||||
|
let errors = {};
|
||||||
|
for (let code of error.error) {
|
||||||
|
errors[code.field] = errors[code.field] || {};
|
||||||
|
errors[code.field][code.code] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let code in errors) {
|
||||||
|
this.form.get(code).setErrors(errors[code]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.tokenInvalid = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<form [formGroup]="form" (ngSubmit)="passwordReset()">
|
<form [formGroup]="form" (ngSubmit)="passwordRequest()">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<h2>{{'password.request' | i18n}}</h2>
|
<h2>{{'password.request' | i18n}}</h2>
|
||||||
@ -10,15 +10,17 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>{{'pgp-key.private' | i18n}}</mat-label>
|
<mat-label>{{'pgp.privateKey' | i18n}}</mat-label>
|
||||||
<textarea matInput formControlName="privateKey" placeholder="Private Key"
|
<textarea matInput formControlName="privateKey" placeholder="Private Key"
|
||||||
[(ngModel)]="model.privateKey"></textarea>
|
[(ngModel)]="model.privateKey"></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
||||||
{{'password.reset' | i18n}}
|
{{'password.request' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</form>
|
</form>
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
import { AuthService } from './../../services/auth.service';
|
import { AuthService } from './../../services/auth.service';
|
||||||
import { MatchingValidator } from './../../utils/matching.validator';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
var openpgp = require('openpgp');
|
var openpgp = require('openpgp');
|
||||||
@Component({
|
@Component({
|
||||||
@ -16,37 +16,39 @@ export class PasswordComponent implements OnInit {
|
|||||||
public working: boolean;
|
public working: boolean;
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder, private authService: AuthService) { }
|
constructor(private formBuilder: FormBuilder, private authService: AuthService, private router: Router) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.form = this.formBuilder.group({
|
this.form = this.formBuilder.group({
|
||||||
username: ['', Validators.required],
|
username: ['', Validators.required],
|
||||||
privateKey: ['', Validators.required]
|
privateKey: ['']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async passwordReset() {
|
async passwordRequest() {
|
||||||
|
this.working = true;
|
||||||
const { keys: [privateKey] } = await openpgp.key.readArmored(this.model.privateKey);
|
const { keys: [privateKey] } = await openpgp.key.readArmored(this.model.privateKey);
|
||||||
|
|
||||||
|
|
||||||
console.log(privateKey.isPrivate());
|
|
||||||
|
|
||||||
const model = {
|
const model = {
|
||||||
username: this.model.username
|
username: this.model.username
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
this.authService.passwordRequest(this.model.username).subscribe(async response => {
|
||||||
|
|
||||||
const message = await openpgp.message.readArmored(encrypted);
|
if (privateKey) {
|
||||||
|
const message = await openpgp.message.readArmored(response);
|
||||||
|
|
||||||
const decrypted = await openpgp.decrypt({
|
const decrypted = await openpgp.decrypt({
|
||||||
message: message,
|
message: message,
|
||||||
privateKeys: [privateKey]
|
privateKeys: [privateKey]
|
||||||
});
|
});
|
||||||
|
this.working = false;
|
||||||
|
this.router.navigate(['/password-reset'], { queryParams: { token: decrypted.data.trim() } });
|
||||||
|
} else {
|
||||||
|
this.working = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
console.log(decrypted);
|
|
||||||
// this.authService.passwordReset(model).subscribe(async response => { })
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<form [formGroup]="form" (ngSubmit)="register()">
|
<form [formGroup]="form" (ngSubmit)="register()" *ngIf="!success">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<h2>{{'register' | i18n}}</h2>
|
<h2>{{'register' | i18n}}</h2>
|
||||||
<mat-error *ngIf="missingToken">
|
<mat-error *ngIf="missingToken">
|
||||||
<a routerLink="/tokens" aria-label="Enter tokens">{{'register.token.missing' | i18n}}</a>
|
<a routerLink="/tokens">{{'register.token.missing' | i18n}}</a>
|
||||||
</mat-error>
|
</mat-error>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput placeholder="{{'username' | i18n}}" formControlName="username"
|
<input matInput placeholder="{{'username' | i18n}}" formControlName="username"
|
||||||
@ -60,3 +60,15 @@
|
|||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<mat-card *ngIf="success">
|
||||||
|
<mat-card-content>
|
||||||
|
<h2>{{'register.success.title' | i18n}}</h2>
|
||||||
|
<p>{{'register.success.text' | i18n}}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<a routerLink="/login" mat-raised-button color="primary">
|
||||||
|
{{'register.login' | i18n}}
|
||||||
|
</a>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
@ -22,6 +22,7 @@ export class RegisterComponent implements OnInit {
|
|||||||
|
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
public missingToken: boolean;
|
public missingToken: boolean;
|
||||||
|
public success: boolean;
|
||||||
public working: boolean;
|
public working: boolean;
|
||||||
items = [];
|
items = [];
|
||||||
currentLocale: String;
|
currentLocale: String;
|
||||||
@ -73,22 +74,38 @@ export class RegisterComponent implements OnInit {
|
|||||||
if (this.form.valid && !this.working) {
|
if (this.form.valid && !this.working) {
|
||||||
this.working = true;
|
this.working = true;
|
||||||
let pgpOption = {
|
let pgpOption = {
|
||||||
userIds: [{ name: this.model.username, email: this.model.email }],
|
userIds: [{ name: this.model.username, email: this.model.username + "@we.bstly.de" }],
|
||||||
numBits: 4096,
|
curve: "ed25519",
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubKey, privKey
|
var pubKey, privKey
|
||||||
openpgp.generateKey(pgpOption).then((key) => {
|
openpgp.generateKey(pgpOption).then((key) => {
|
||||||
privKey = key.privateKeyArmored
|
privKey = key.privateKeyArmored;
|
||||||
pubKey = key.publicKeyArmored
|
pubKey = key.publicKeyArmored;
|
||||||
this.model.publicKey = pubKey;
|
this.model.publicKey = pubKey;
|
||||||
this.userService.register(this.model).subscribe((result: any) => {
|
this.userService.register(this.model).subscribe((result: any) => {
|
||||||
result.privateKey = privKey;
|
result.privateKey = privKey;
|
||||||
|
|
||||||
|
var element = document.createElement('a');
|
||||||
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(privKey));
|
||||||
|
element.setAttribute('download', result.username + ".private.key");
|
||||||
|
element.style.display = 'none';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
document.body.removeChild(element);
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(RegisterDialog, {
|
const dialogRef = this.dialog.open(RegisterDialog, {
|
||||||
closeOnNavigation: false,
|
closeOnNavigation: false,
|
||||||
disableClose: true,
|
disableClose: true,
|
||||||
data: result
|
data: result
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.success = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.working = false;
|
this.working = false;
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
this.working = false;
|
this.working = false;
|
||||||
@ -106,7 +123,6 @@ export class RegisterComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,9 +140,4 @@ export class RegisterDialog {
|
|||||||
public dialogRef: MatDialogRef<RegisterDialog>,
|
public dialogRef: MatDialogRef<RegisterDialog>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||||
|
|
||||||
onOkClick(): void {
|
|
||||||
this.dialogRef.close();
|
|
||||||
this.router.navigate(["/login"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
<h1 mat-dialog-title>{{data.username}}</h1>
|
<h1 mat-dialog-title>{{data.username}}</h1>
|
||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<h3>Permissions</h3>
|
<h3>{{'permissions' | i18n}}</h3>
|
||||||
<app-permissions [permissions]="data.permissions"></app-permissions>
|
<app-permissions [permissions]="data.permissions"></app-permissions>
|
||||||
<h3>Quotas</h3>
|
<h3>{{'quotas' | i18n}}</h3>
|
||||||
<app-quotas [quotas]="data.quotas"></app-quotas>
|
<app-quotas [quotas]="data.quotas"></app-quotas>
|
||||||
|
<h3>{{'pgp.privateKey' | i18n}}</h3>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Private PGP key</mat-label>
|
<qrcode [qrdata]="data.privateKey" [width]="400" [errorCorrectionLevel]="'M'"></qrcode>
|
||||||
|
<mat-label>{{'pgp.privateKey' | i18n}}</mat-label>
|
||||||
<textarea matInput readonly [(ngModel)]="data.privateKey"></textarea>
|
<textarea matInput readonly [(ngModel)]="data.privateKey"></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
<mat-slide-toggle [(ngModel)]="data.confirmClose">
|
<mat-slide-toggle [(ngModel)]="data.confirmClose">
|
||||||
I have saved my private key securely!
|
{{'pgp.privateKey.confirmStore' | i18n}}
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
|
|
||||||
<button mat-button (click)="onOkClick()" [disabled]="!data.confirmClose">Ok</button>
|
<button mat-button [disabled]="!data.confirmClose" [mat-dialog-close]="true">{{'ok' | i18n}}</button>
|
||||||
</div>
|
</div>
|
@ -49,8 +49,6 @@ export class UsernameDialog {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.username = uniqueNamesGenerator(config);
|
this.username = uniqueNamesGenerator(config);
|
||||||
|
|
||||||
console.log(this.username);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(dict) {
|
toggle(dict) {
|
||||||
|
@ -46,7 +46,8 @@
|
|||||||
<a *ngIf="!auth.authenticated" routerLink="/register" mat-raised-button color="accent">
|
<a *ngIf="!auth.authenticated" routerLink="/register" mat-raised-button color="accent">
|
||||||
<mat-icon>how_to_reg</mat-icon> {{'register' | i18n}}
|
<mat-icon>how_to_reg</mat-icon> {{'register' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
<a *ngIf="!auth.authenticated" routerLink="/login" mat-raised-button color="primary">
|
<a *ngIf="!auth.authenticated" routerLink="/login" [queryParams]="{ target:'tokens' }" mat-raised-button
|
||||||
|
color="primary">
|
||||||
<mat-icon>login</mat-icon> {{'login' | i18n}}
|
<mat-icon>login</mat-icon> {{'login' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
|
@ -97,7 +97,7 @@ export class TokensComponent implements OnInit {
|
|||||||
redeem() {
|
redeem() {
|
||||||
if (this.auth.authenticated) {
|
if (this.auth.authenticated) {
|
||||||
this.itemService.redeem().subscribe((data: any) => {
|
this.itemService.redeem().subscribe((data: any) => {
|
||||||
|
this.router.navigate(["/account/info"]);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,22 +27,41 @@ export class AuthService {
|
|||||||
return this.http.get(environment.apiUrl + "/auth/me");
|
return this.http.get(environment.apiUrl + "/auth/me");
|
||||||
}
|
}
|
||||||
|
|
||||||
login(username, password) {
|
login(loginModel) {
|
||||||
return this.http.post(environment.apiUrl + "/auth/login", { username: username, password: password });
|
return this.http.post(environment.apiUrl + "/auth/login", loginModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
return this.http.post(environment.apiUrl + "/auth/logout", {});
|
return this.http.post(environment.apiUrl + "/auth/logout", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordRequest() {
|
passwordRequest(username) {
|
||||||
return this.http.post(environment.apiUrl + "/auth/password/request", {});
|
const headers = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8');
|
||||||
|
return this.http.post(environment.apiUrl + "/auth/password/request", username, { headers, responseType: 'text' });
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordReset(model) {
|
passwordReset(model) {
|
||||||
const headers = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8');
|
const headers = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8');
|
||||||
return this.http.post(environment.apiUrl + "/auth/password/reset", model,
|
return this.http.post(environment.apiUrl + "/auth/password/reset", model);
|
||||||
{ headers, responseType: 'text' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTotpEnabled() {
|
||||||
|
return this.http.get(environment.apiUrl + "/auth/totp");
|
||||||
|
}
|
||||||
|
|
||||||
|
createTotp() {
|
||||||
|
return this.http.put(environment.apiUrl + "/auth/totp", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
enableTotp(code) {
|
||||||
|
return this.http.patch(environment.apiUrl + "/auth/totp", code);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTotp() {
|
||||||
|
return this.http.delete(environment.apiUrl + "/auth/totp");
|
||||||
|
}
|
||||||
|
|
||||||
|
loginTotp(totpModel) {
|
||||||
|
return this.http.post(environment.apiUrl + "/auth/login/totp", totpModel);
|
||||||
|
}
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import { isEmpty } from 'rxjs/operators';
|
|||||||
export class I18nService {
|
export class I18nService {
|
||||||
|
|
||||||
locale: String;
|
locale: String;
|
||||||
|
locales = ["de-informal"];
|
||||||
i18n: any;
|
i18n: any;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
@ -17,15 +18,24 @@ export class I18nService {
|
|||||||
browserLocale = browserLocale.split("-")[0];
|
browserLocale = browserLocale.split("-")[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
let locale = localStorage.getItem("bstly.locale") || browserLocale || 'en';
|
let locale = localStorage.getItem("bstly.locale") || browserLocale || this.locales[0];
|
||||||
|
|
||||||
if (locale == 'de') {
|
if (locale == 'de') {
|
||||||
locale = 'de-informal';
|
locale = 'de-informal';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.locales.indexOf(locale) == -1) {
|
||||||
|
locale = this.locales[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.setLocale(locale);
|
this.setLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLocales() {
|
||||||
|
return this.locales;
|
||||||
|
}
|
||||||
|
|
||||||
getLocale() {
|
getLocale() {
|
||||||
return this.locale;
|
return this.locale;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,10 @@ export class UserService {
|
|||||||
return this.http.post(environment.apiUrl + "/users", userModel);
|
return this.http.post(environment.apiUrl + "/users", userModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkModel(userModel) {
|
||||||
|
return this.http.post(environment.apiUrl + "/users/model", userModel);
|
||||||
|
}
|
||||||
|
|
||||||
password(passwordModel) {
|
password(passwordModel) {
|
||||||
return this.http.patch(environment.apiUrl + "/users/password", passwordModel);
|
return this.http.patch(environment.apiUrl + "/users/password", passwordModel);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<table mat-table [dataSource]="permissions">
|
<table mat-table matSort [dataSource]="permissions" (matSortChange)="sortData($event)">
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{'permissions.name' | i18n}} </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'permissions.name' | i18n}} </th>
|
||||||
<td mat-cell *matCellDef="let permission"> {{permission.name}} <mat-icon *ngIf="permission.addon" aria-hidden="false" aria-label="Add-on">add_circle</mat-icon></td>
|
<td mat-cell *matCellDef="let permission"> {{'permissions.' + permission.name | i18n}}
|
||||||
|
<mat-icon *ngIf="permission.addon">add_circle</mat-icon>
|
||||||
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="expires">
|
<ng-container matColumnDef="expires">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{'permissions.expires' | i18n}} </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="expires"> {{'permissions.expires' | i18n}} </th>
|
||||||
<td mat-cell *matCellDef="let permission">{{permission.expires | date}}</td>
|
<td mat-cell *matCellDef="let permission">{{permission.expires | date}}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Sort } from '@angular/material/sort';
|
||||||
|
import { I18nService } from './../../services/i18n.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-permissions',
|
selector: 'app-permissions',
|
||||||
@ -7,12 +9,33 @@ import { Component, OnInit, Input } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class PermissionsComponent implements OnInit {
|
export class PermissionsComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
@Input() permissions;
|
@Input() permissions;
|
||||||
permissionColumns = ["name", "expires"];
|
permissionColumns = ["name", "expires"];
|
||||||
|
|
||||||
constructor() { }
|
constructor(private i18n: I18nService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
const data = this.permissions.slice();
|
||||||
|
if (!sort.active || sort.direction === '') {
|
||||||
|
this.permissions = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.permissions = data.sort((a, b) => {
|
||||||
|
const isAsc = sort.direction === 'asc';
|
||||||
|
switch (sort.active) {
|
||||||
|
case 'name': return this.compare(this.i18n.get('permissions.' + a.name,[]), this.i18n.get('permissions.' + b.name,[]), isAsc);
|
||||||
|
case 'expires': return this.compare(a.expires, b.expires, isAsc);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(a: number | string | String, b: number | string | String, isAsc: boolean) {
|
||||||
|
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
<table mat-table [dataSource]="quotas">
|
<table mat-table matSort [dataSource]="quotas" (matSortChange)="sortData($event)">
|
||||||
|
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{'quotas.name' | i18n}} </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'quotas.name' | i18n}} </th>
|
||||||
<td mat-cell *matCellDef="let quota"> {{quota.name}} </td>
|
<td mat-cell *matCellDef="let quota"> {{'quotas.' + quota.name | i18n}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="quota">
|
<ng-container matColumnDef="quota">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{'quotas.value' | i18n}} </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="value"> {{'quotas.value' | i18n}} </th>
|
||||||
<td mat-cell *matCellDef="let quota">{{quota.value}} {{quota.unit}}</td>
|
<td mat-cell *matCellDef="let quota"> {{quota.value}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="quotaUnit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{'quotas.unit' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let quota">
|
||||||
|
<span *ngIf="quota.unit">{{'quotas.unit.' + quota.unit | i18n}}</span>
|
||||||
|
<span *ngIf="!quota.unit">#</span>
|
||||||
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="quotaColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="quotaColumns"></tr>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Sort } from '@angular/material/sort';
|
||||||
|
import { I18nService } from './../../services/i18n.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-quotas',
|
selector: 'app-quotas',
|
||||||
@ -7,11 +9,34 @@ import { Component, OnInit, Input } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class QuotasComponent implements OnInit {
|
export class QuotasComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
@Input() quotas;
|
@Input() quotas;
|
||||||
quotaColumns = ["name", "quota"];
|
quotaColumns = ["name", "quota", "quotaUnit"];
|
||||||
constructor() { }
|
|
||||||
|
constructor(private i18n: I18nService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
const data = this.quotas.slice();
|
||||||
|
if (!sort.active || sort.direction === '') {
|
||||||
|
this.quotas = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.quotas = data.sort((a, b) => {
|
||||||
|
const isAsc = sort.direction === 'asc';
|
||||||
|
switch (sort.active) {
|
||||||
|
case 'name': return this.compare(this.i18n.get('quotas.' + a.name, []), this.i18n.get('quotas.' + b.name,[]), isAsc);
|
||||||
|
case 'value': return this.compare(a.value, b.value, isAsc);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(a: number | string | String, b: number | string | String, isAsc: boolean) {
|
||||||
|
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ export class HtmlComponent implements OnInit {
|
|||||||
const headers = new HttpHeaders()
|
const headers = new HttpHeaders()
|
||||||
.set('content-type', 'text/html');
|
.set('content-type', 'text/html');
|
||||||
this.httpClient.get(
|
this.httpClient.get(
|
||||||
'./assets/templates/' + this.template + (this.locale ? "." + this.locale : "") + ".html",
|
'./assets/templates/' + (this.locale ? this.locale + "/" : "") + this.template + ".html",
|
||||||
{
|
{
|
||||||
headers: headers,
|
headers: headers,
|
||||||
responseType: 'text'
|
responseType: 'text'
|
||||||
|
@ -1,11 +1,47 @@
|
|||||||
{
|
{
|
||||||
"i18n.test.replace": "Wat!?! {0} {1} {2}",
|
"i18n.test.replace": "Wat!?! {0} {1} {2}",
|
||||||
"greet": "Hallo {0}",
|
"greet": "Hallo {0}",
|
||||||
"home": "Start",
|
"home": {
|
||||||
|
".": "Über we.bstly",
|
||||||
|
"general": {
|
||||||
|
".": "Über we.bstly",
|
||||||
|
"what": "Was wir machen",
|
||||||
|
"you": "Was du machen kannst",
|
||||||
|
"we": "Was unser Ziel ist"
|
||||||
|
},
|
||||||
|
"privacy": {
|
||||||
|
".": "Datenschutz",
|
||||||
|
"design": "Privacy By Design",
|
||||||
|
"pretix": "Shop System (Pretix)",
|
||||||
|
"we-bstly": "we.bstly",
|
||||||
|
"services": "Aktuelle Services"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
".": "Services",
|
||||||
|
"active": "Aktive Services",
|
||||||
|
"planned": "Geplante Services",
|
||||||
|
"email": "E-Mail Postfach",
|
||||||
|
"legend": {
|
||||||
|
".": "Legende",
|
||||||
|
"ready": "✅ fertig, benötigt nur Finanzierung",
|
||||||
|
"not-ready": "❔ noch nicht fertig",
|
||||||
|
"not-available": "⚠️ noch nicht konkret/technische Hürden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"club": {
|
||||||
|
".": "Verein",
|
||||||
|
"membership": "Mitgliedschaft",
|
||||||
|
"charter": "Satzung (Entwurf)",
|
||||||
|
"about": "Über den Verein"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {},
|
||||||
|
"cancel": "Abbrechen",
|
||||||
"login": {
|
"login": {
|
||||||
".": "Login",
|
".": "Login",
|
||||||
"external": "Login",
|
"external": "Login",
|
||||||
"invalid": "Falscher Username oder Passwort."
|
"invalid": "Falscher Username oder Passwort.",
|
||||||
|
"keepSession": "Eingelogged bleiben"
|
||||||
},
|
},
|
||||||
"not-found": {
|
"not-found": {
|
||||||
".": "Nicht gefunden",
|
".": "Nicht gefunden",
|
||||||
@ -23,22 +59,29 @@
|
|||||||
".": "Tokens",
|
".": "Tokens",
|
||||||
"redeem": "Tokens einlösen",
|
"redeem": "Tokens einlösen",
|
||||||
"redeemed": "Das Token wurde bereits eingelöst.",
|
"redeemed": "Das Token wurde bereits eingelöst.",
|
||||||
"get": "Tokens holen",
|
"get": "Mitmachen",
|
||||||
"enter": "Token eingeben",
|
"enter": "Token eingeben",
|
||||||
"validate": "Prüfen",
|
"validate": "Prüfen",
|
||||||
"invalid" : "Das Token ist leider nicht gültig.",
|
"invalid": "Das Token ist leider nicht gültig.",
|
||||||
"provide-valid" : "Bitte gebe ein gültiges Token ein."
|
"provide-valid": "Bitte gebe ein gültiges Token ein."
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
".": "Username",
|
".": "Username",
|
||||||
"missing": "Bitte gebe einen Usernamen an.",
|
"missing": "Bitte gebe einen Usernamen an.",
|
||||||
"error" : "Bitte wähle einen anderen Usernamen aus."
|
"error": "Bitte wähle einen anderen Usernamen aus."
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
".": "Passwort",
|
".": "Passwort",
|
||||||
"forgot": "Passwort vergessen",
|
"forgot": "Passwort vergessen",
|
||||||
"request": "Neues Passwort anfordern",
|
"request": "Neues Passwort anfordern",
|
||||||
"reset": "Passwort setzen",
|
"reset": {
|
||||||
|
".": "Passwort setzen",
|
||||||
|
"login": "Zum Login",
|
||||||
|
"success": {
|
||||||
|
"title": "Passwort erfolgreich geändert",
|
||||||
|
"text": "Dein neues Passwort wurde übernommen. Du kannst dich nun mit deinem neuen Passwort einloggen."
|
||||||
|
}
|
||||||
|
},
|
||||||
"change": "Passwort ändern",
|
"change": "Passwort ändern",
|
||||||
"changed": "Passwort erfolgreich geändert",
|
"changed": "Passwort erfolgreich geändert",
|
||||||
"current": "Akutelles Passwort",
|
"current": "Akutelles Passwort",
|
||||||
@ -47,36 +90,53 @@
|
|||||||
"invalid": {
|
"invalid": {
|
||||||
"hint": "Bitte gebe das Passwort in einem gültigen Format an."
|
"hint": "Bitte gebe das Passwort in einem gültigen Format an."
|
||||||
},
|
},
|
||||||
"error" : {
|
"error": {
|
||||||
"ILLEGAL_WHITESPACE" : "Bitte keine Leerzeichen verwenden.",
|
"ILLEGAL_WHITESPACE": "Bitte keine Leerzeichen verwenden.",
|
||||||
"INSUFFICIENT_DIGIT" : "Bitte mindestens eine Zahl eingeben.",
|
"INSUFFICIENT_DIGIT": "Bitte mindestens eine Zahl eingeben.",
|
||||||
"INSUFFICIENT_UPPERCASE" : "Bitte mindestens einen Großbuchstaben eingeben.",
|
"INSUFFICIENT_UPPERCASE": "Bitte mindestens einen Großbuchstaben eingeben.",
|
||||||
"INSUFFICIENT_LOWERCASE" : "Bitte mindestens einen Kleinbuchstaben eingeben.",
|
"INSUFFICIENT_LOWERCASE": "Bitte mindestens einen Kleinbuchstaben eingeben.",
|
||||||
"INSUFFICIENT_SPECIAL" : "Bitte mindestens ein Sonderzeichen eingeben.",
|
"INSUFFICIENT_SPECIAL": "Bitte mindestens ein Sonderzeichen eingeben.",
|
||||||
"TOO_SHORT" : "Bitte ein längeres Passwort wählen."
|
"TOO_SHORT": "Bitte ein längeres Passwort wählen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
".": "Registrierung",
|
".": "Registrierung",
|
||||||
"token.missing": "Du benötigst leider ein gültiges Token!"
|
"token.missing": "Du benötigst leider ein gültiges Token!",
|
||||||
|
"login": "Zum Login",
|
||||||
|
"success": {
|
||||||
|
"title": "Registrierung abgeschlossen",
|
||||||
|
"text": "Deine Registrierung war erfolgreich. Du kannst dich nun einloggen!"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
".": "E-Mail Adresse",
|
".": "E-Mail Adresse",
|
||||||
"primary": "primäre E-Mail Adresse",
|
"primary": "primäre E-Mail Adresse",
|
||||||
"invalid": "ungültige E-Mail Adresse"
|
"invalid": "ungültige E-Mail Adresse"
|
||||||
},
|
},
|
||||||
"apps": "Apps",
|
"apps": {
|
||||||
"app": {
|
".": "Apps",
|
||||||
"goto": "Gehe zur App"
|
"goto": "Gehe zur App",
|
||||||
|
"nextcloud": {
|
||||||
|
"icon": "cloud",
|
||||||
|
"title": "Nextcloud",
|
||||||
|
"subtitle": "wolkige bstly",
|
||||||
|
"text": "Nextcloud bietet dir Dateiverwaltung, Kalendar, Aufgabenmanagement, Kontaktmanagement, Kommunikationskanäle und Abstimmungen."
|
||||||
|
},
|
||||||
|
"mail": {
|
||||||
|
"icon": "email",
|
||||||
|
"title": "E-Mail Postfach",
|
||||||
|
"subtitle": "E-Mail wie es sein sollte",
|
||||||
|
"text": "Catch-All an @{username}.we.bstly.de, lernender Spam-Filter und PGP Verschlüsselung."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"en": {
|
"en": {
|
||||||
"short": "EN",
|
"short": "EN",
|
||||||
"long": "Englisch"
|
"long": "English"
|
||||||
},
|
},
|
||||||
"de-informal": {
|
"de-informal": {
|
||||||
"short": "DE",
|
"short": "DE",
|
||||||
"long": "German"
|
"long": "Deutsch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
@ -85,12 +145,23 @@
|
|||||||
"permissions": {
|
"permissions": {
|
||||||
".": "Berechtigungen",
|
".": "Berechtigungen",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"expires": "Gültig bis"
|
"expires": "Gültig bis",
|
||||||
|
"nextcloud": "Nextcloud",
|
||||||
|
"mail": "E-Mail Postfach",
|
||||||
|
"ROLE_MEMBER": "Vereinsmitgliedschaft"
|
||||||
},
|
},
|
||||||
"quotas": {
|
"quotas": {
|
||||||
".": "Quotas",
|
".": "Quotas",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"value": "Quota"
|
"value": "Quota",
|
||||||
|
"unit": {
|
||||||
|
".": "Einheit",
|
||||||
|
"G": "GB (Gigabyte)",
|
||||||
|
"#": "# (Anzahl)"
|
||||||
|
},
|
||||||
|
"nextcloud": "Nextcloud",
|
||||||
|
"mail": "E-Mail Postfach",
|
||||||
|
"registration_vouchers": "Registrierungs-Gutscheincodes"
|
||||||
},
|
},
|
||||||
"voucher": {
|
"voucher": {
|
||||||
".": "Gutscheincode",
|
".": "Gutscheincode",
|
||||||
@ -99,7 +170,7 @@
|
|||||||
},
|
},
|
||||||
"vouchers": {
|
"vouchers": {
|
||||||
".": "Gutscheincodes",
|
".": "Gutscheincodes",
|
||||||
"info": "Gutscheincodes für Add-Ons und Registrierung",
|
"info": "Hier kannst du Gutscheincodes für Add-Ons und Registrierung generieren.",
|
||||||
"registration": "Registrierung",
|
"registration": "Registrierung",
|
||||||
"add-on": "Add-On",
|
"add-on": "Add-On",
|
||||||
"temp": {
|
"temp": {
|
||||||
@ -112,6 +183,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
".": "Sicherheit"
|
".": "Sicherheit",
|
||||||
}
|
"2fa": {
|
||||||
|
".": "Zwei-Faktor-Authentifierung (2FA)",
|
||||||
|
"info": "Du kannst hier einen zweiten Faktor zusätzlich zu deinem Passwort hinzufügen. Beachte, dass dies nur den Login in deinen we.bstly-Account betrifft. 2FA gilt nicht für deinen E-Mail Account. Aktuell wird nur TOTP (bekannt als Google Authenticator) unterstützt.",
|
||||||
|
"totp": {
|
||||||
|
".": "2FA (TOTP)",
|
||||||
|
"hint": "Um TOTP als zweiten Faktor beim Login zu verwenden, scanne den QRCode mit deiner TOTP App.",
|
||||||
|
"enable": "Aktiviere 2FA (TOTP)",
|
||||||
|
"code": "TOTP Code",
|
||||||
|
"login": "Code verfizieren",
|
||||||
|
"create": "2FA (TOTP) einrichten",
|
||||||
|
"remove": "2FA (TOTP) deaktivieren",
|
||||||
|
"external": "2FA (TOTP)",
|
||||||
|
"invalid": "TOTP Code ist ungültig",
|
||||||
|
"missing": "Bitte TOTP Code eingeben",
|
||||||
|
"activate": "Um TOTP als 2FA zu aktivieren, gebe bitte deinen aktuellen Code ein.",
|
||||||
|
"keepSession": "2FA (TOTP) für dieses Gerät merken"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pgp": {
|
||||||
|
".": "PGP",
|
||||||
|
"privateKey": {
|
||||||
|
".": "Privater PGP Schlüssel",
|
||||||
|
"confirmStore": "Ich habe meinen privaten Schlüssel sicher gespeichert!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"software": "Software",
|
||||||
|
"sourcecode": "Quellcode",
|
||||||
|
"homepage": "Homepage",
|
||||||
|
"imprint": "Impressum",
|
||||||
|
"privacy-policy": "Datenschutzerklärung"
|
||||||
}
|
}
|
@ -2,11 +2,24 @@
|
|||||||
"i18n.test.replace": "yes no it's clear! {0} {1} {2}",
|
"i18n.test.replace": "yes no it's clear! {0} {1} {2}",
|
||||||
"greet": "Hello {0}",
|
"greet": "Hello {0}",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
"cancel" : "Cancel",
|
||||||
"login": {
|
"login": {
|
||||||
".": "Login",
|
".": "Login",
|
||||||
"external": "Login",
|
"external": "Login",
|
||||||
"invalid": "The username and password were not recognised."
|
"invalid": "The username and password were not recognised."
|
||||||
},
|
},
|
||||||
|
"totp": {
|
||||||
|
".": "2FA (TOTP)",
|
||||||
|
"hint": "For using TOTP (known as Google Authenticator) as second factor, please scan the QRCode with your TOTP App.",
|
||||||
|
"enable": "Enable 2FA (TOTP)",
|
||||||
|
"code": "TOTP code",
|
||||||
|
"login" : "Login",
|
||||||
|
"create": "Setup 2FA (TOTP)",
|
||||||
|
"remove": "Disable 2FA (TOTP)",
|
||||||
|
"external": "2FA (TOTP)",
|
||||||
|
"invalid": "TOTP code is invalid",
|
||||||
|
"missing": "TOTP code is missing"
|
||||||
|
},
|
||||||
"not-found": {
|
"not-found": {
|
||||||
".": "Not Found",
|
".": "Not Found",
|
||||||
"text": "What's Up!?"
|
"text": "What's Up!?"
|
||||||
@ -26,13 +39,13 @@
|
|||||||
"get": "Get Tokens",
|
"get": "Get Tokens",
|
||||||
"enter": "Enter Token",
|
"enter": "Enter Token",
|
||||||
"validate": "Validate",
|
"validate": "Validate",
|
||||||
"invalid" : "The provided token is invalid.",
|
"invalid": "The provided token is invalid.",
|
||||||
"provide-valid" : "Please provide a valid token"
|
"provide-valid": "Please provide a valid token"
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
".": "Username",
|
".": "Username",
|
||||||
"missing": "Please provide a valid username.",
|
"missing": "Please provide a valid username.",
|
||||||
"error" : "Please choose another username."
|
"error": "Please choose another username."
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
".": "Password",
|
".": "Password",
|
||||||
@ -47,13 +60,13 @@
|
|||||||
"invalid": {
|
"invalid": {
|
||||||
"hint": "Please provide a valid password"
|
"hint": "Please provide a valid password"
|
||||||
},
|
},
|
||||||
"error" : {
|
"error": {
|
||||||
"ILLEGAL_WHITESPACE" : "Please do not use any whitespace!",
|
"ILLEGAL_WHITESPACE": "Please do not use any whitespace!",
|
||||||
"INSUFFICIENT_DIGIT" : "Please provide at least one digit!",
|
"INSUFFICIENT_DIGIT": "Please provide at least one digit!",
|
||||||
"INSUFFICIENT_UPPERCASE" : "Please provide at leaste one uppercase character!",
|
"INSUFFICIENT_UPPERCASE": "Please provide at leaste one uppercase character!",
|
||||||
"INSUFFICIENT_LOWERCASE" : "Please provide at leaste one lowercase character!",
|
"INSUFFICIENT_LOWERCASE": "Please provide at leaste one lowercase character!",
|
||||||
"INSUFFICIENT_SPECIAL" : "Please provide at leaste one special character!",
|
"INSUFFICIENT_SPECIAL": "Please provide at leaste one special character!",
|
||||||
"TOO_SHORT" : "Please choose a longer password!"
|
"TOO_SHORT": "Please choose a longer password!"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
@ -113,5 +126,8 @@
|
|||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
".": "Security"
|
".": "Security"
|
||||||
}
|
},
|
||||||
|
"software" : "Software",
|
||||||
|
"sourcecode": "Sourcecode",
|
||||||
|
"homepage": "Homepage"
|
||||||
}
|
}
|
@ -1,3 +0,0 @@
|
|||||||
<h2>we.bstly</h2>
|
|
||||||
<p>Willkommen zur digitalen Bastelei.</p>
|
|
||||||
<p></p>
|
|
@ -1,3 +0,0 @@
|
|||||||
<h2>we.bstly</h2>
|
|
||||||
|
|
||||||
<p>Welcome to the digital tinkering.</p>
|
|
2
src/assets/templates/de-informal/about.html
Normal file
2
src/assets/templates/de-informal/about.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<h2>we.bstly</h2>
|
||||||
|
<p>Willkommen zu 'Bastelei (bald e.V.)'.</p>
|
1
src/assets/templates/de-informal/club/about.html
Normal file
1
src/assets/templates/de-informal/club/about.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>Der eingetragene Verein bietet sich für unsere Zwecke als Rechtsform an, da wir nicht wirtschaftlich orientiert arbeiten wollen, sondern ideelle Ziele haben. Wir haben uns allerdings bewusst gegen eine Gemeinnützigkeit im Sinne des Vereinsgesetzes entschieden. Dies hat rechtliche Gründe, da wir uns selbst sehr wohl als Gemeinnützig betrachten würden. Die Erfahrung mit der Arbeit in gemeinnützigen Vereinen sowie die aktuellen Rechtsprechungen, die nicht mit unserem Verständnis von Gemeinnützigkeit übereinstimmen, haben uns jedoch zu dem Schluss kommen lassen, dass wir von Anfang an auf den juristischen Anspruch der Gemeinnützigkeit verzichten werden. Der offensichtliche Nachteil besteht vor allem im Verzicht auf Steuerbegünstigungen, dies gilt auch z.B. für die Steuererstattung von Mitgliedsbeiträgen, die hier entfällt. Auf der anderen Seite können wir jetzt einfacher Rücklagen bilden, Mitglieder für Arbeit entlohnen und freier Entscheidungen treffen, so dass wir unsere Ziele einfacher und schneller erreichen können.</p>
|
202
src/assets/templates/de-informal/club/charter.html
Normal file
202
src/assets/templates/de-informal/club/charter.html
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
<h1>Satzung</h1>
|
||||||
|
<p>des “Bastelei e.V.”</p>
|
||||||
|
|
||||||
|
<h3>Präambel</h3>
|
||||||
|
|
||||||
|
<p>Die ökologischen, ökonomischen und kulturellen Probleme unserer Zeit stellen uns vor die Aufgabe, unsere gesamte
|
||||||
|
Kultur und Technologie auf nachhaltigere, ethische und zivilisiertere Grundlagen zu stellen. Diese Probleme durch
|
||||||
|
globale Institutionen anzugehen zeitigt dabei nur bedingt Erfolge, Dezentralisierung ist Teil des Kulturbegriffs,
|
||||||
|
eine zentrale Kultur gibt es nicht.</p>
|
||||||
|
|
||||||
|
<p>Die Zukunft ist keine Errungenschaft einer Elite oder eine technische Ingenieursleistung sondern eine Reihe von
|
||||||
|
Möglichkeiten die jeder einzelne Mensch durch sein Handeln gestalten kann indem er seine Kreativität und Solidarität
|
||||||
|
entwickelt und die friedliche Gemeinschaft mit anderen Lebewesen unabhängig von Alter, Geschlecht und Abstammung
|
||||||
|
sowie gesellschaftlicher Stellung pflegt.</p>
|
||||||
|
|
||||||
|
<p>Die Kommende Bastelei möchte diesen Bestrebungen zur Gestaltung einer lebenswerten und kulturreichen Zukunft in ihrem
|
||||||
|
Rahmen und mit den Mitteln der Bastelei eine organisatorische Grundlage bieten. Der Verein stellt seinen Mitgliedern
|
||||||
|
deswegen eine organisatorische und institutionelle Infrastruktur zur Verfügung um ihre Projekte und Ideen
|
||||||
|
selbstständig verfolgen zu können.</p>
|
||||||
|
|
||||||
|
<h2>§1 Name, Sitz, Geschäftsjahr</h2>
|
||||||
|
<p>Der Verein führt den Namen "Bastelei". Der Verein wird in das Vereinsregister eingetragen und dann um den Zusatz „e.
|
||||||
|
V.“ ergänzt. </p>
|
||||||
|
|
||||||
|
<p>Der Verein hat seinen Sitz in Gevelsberg.</p>
|
||||||
|
|
||||||
|
<p>Das Geschäftsjahr ist das Kalenderjahr.</p>
|
||||||
|
|
||||||
|
<h2>§2 Zweck</h2>
|
||||||
|
<p>Zweck des Vereins ist die gemeinsame Arbeit an der Organisation, Entwicklung, Bereitstellung und dem Betrieb offener,
|
||||||
|
kollaborativ genutzter technischer Infrastruktur sowie der Ausrichtung von Veranstaltungen zur Förderung des
|
||||||
|
nachhaltigen Umgangs mit Technologien und der informationellen Selbstbestimmung aus ideellem Interesse. Hierbei
|
||||||
|
werden alle rassistischen, faschistischen und sexistischen Strömungen ausgeschlossen.</p>
|
||||||
|
|
||||||
|
<h2>§3 Mitgliedschaft</h2>
|
||||||
|
<p>Ordentliche Vereinsmitglieder können ausschließlich natürliche Personen werden.</p>
|
||||||
|
|
||||||
|
<p>Die Beitrittserklärung erfolgt in Textform gegenüber dem Vorstand. Über die Annahme der Beitrittserklärung
|
||||||
|
entscheidet der Vorstand. Die Mitgliedschaft beginnt mit der vorläufigen Annahme der Beitrittserklärung und der
|
||||||
|
Zahlung des ersten Beitrages im Voraus. Die Vorläufigkeit endet mit der Bestätigung des Mitglieds durch den
|
||||||
|
Vorstand.</p>
|
||||||
|
|
||||||
|
<p>Die Mitgliedschaft endet durch Austrittserklärung, durch Tod von natürlichen Personen oder durch Auflösung und
|
||||||
|
Erlöschen von juristischen Personen, Handelsgesellschaften, nicht rechtsfähigen Vereinen sowie Anstalten und
|
||||||
|
Körperschaften des öffentlichen Rechts oder durch Ausschluss; die Beitragspflicht für das laufende Beitragsjahr
|
||||||
|
bleibt hiervon unberührt.</p>
|
||||||
|
|
||||||
|
<p>Der Austritt wird durch Willenserklärung in Textform gegenüber dem Vorstand vollzogen.</p>
|
||||||
|
|
||||||
|
<p>Die Mitgliederversammlung kann solche Personen, die sich besondere Verdienste um den Verein oder um die von ihm
|
||||||
|
verfolgten satzungsgemäßen Zwecke erworben haben, zu Ehrenmitgliedern ernennen. Ehrenmitglieder haben alle Rechte
|
||||||
|
eines ordentlichen Mitglieds. Sie sind von Beitragsleistungen befreit.</p>
|
||||||
|
|
||||||
|
<p>Fördermitglieder sind passive Mitglieder ohne Stimmrecht in der Mitgliederversammlung. Fördermitglieder können
|
||||||
|
ausschließlich natürliche Personen werden. Bei Minderjährigen ist die Zustimmung des gesetzlichen Vertreters
|
||||||
|
erforderlich.</p>
|
||||||
|
|
||||||
|
<h2>§4 Rechte und Pflichten der Mitglieder</h2>
|
||||||
|
<p>Die Mitglieder sind berechtigt, die Leistungen des Vereins in Anspruch zu nehmen.</p>
|
||||||
|
|
||||||
|
<p>Die Mitglieder sind verpflichtet, die satzungsgemäßen Zwecke des Vereins zu unterstützen und zu fördern. Sie sind
|
||||||
|
verpflichtet, die festgesetzten Beiträge zu zahlen.</p>
|
||||||
|
|
||||||
|
<h2>§5 Ausschluss eines Mitglieds</h2>
|
||||||
|
<p>Ein Mitglied kann durch Beschluss des Vorstandes ausgeschlossen werden, wenn es das Ansehen des Vereins schädigt,
|
||||||
|
seinen Beitragsverpflichtungen nicht nachkommt oder wenn ein sonstiger wichtiger Grund vorliegt. Der Vorstand muss
|
||||||
|
dem auszuschließenden Mitglied den Beschluss in Textform unter Angabe von Gründen, an die letzte bekannte Anschrift
|
||||||
|
oder an die zuletzt bekannte E-Mail-Adresse, mitteilen und ihm auf Verlangen eine Anhörung gewähren.</p>
|
||||||
|
|
||||||
|
<p>Gegen den Beschluss des Vorstandes kann das auszuschließende Mitglied die Mitgliederversammlung anrufen. Bis zum
|
||||||
|
Beschluss der Mitgliederversammlung ruht die Mitgliedschaft. Die Anrufung muss innerhalb einer Frist von vier Wochen
|
||||||
|
ab Zugang des Ausschließungsbeschlusses in Textform beim Vorstand eingelegt werden. Erfolgt keine Anrufung oder
|
||||||
|
verstreicht die Frist, gilt die Mitgliedschaft ab dem Zeitpunkt des Ausschlusses als beendet.</p>
|
||||||
|
|
||||||
|
<h2>§6 Beitrag</h2>
|
||||||
|
<p>Der Verein erhebt Mitgliedsbeiträge. Das Nähere regelt eine Beitragsordnung, die von der Mitgliederversammlung
|
||||||
|
beschlossen wird. Im Falle nicht fristgerechter Entrichtung der Beiträge ruht die Mitgliedschaft.</p>
|
||||||
|
|
||||||
|
<p>Im begründeten Einzelfall kann für ein Mitglied durch Vorstandsbeschluss ein von der Beitragsordnung abweichender
|
||||||
|
Beitrag festgesetzt werden.
|
||||||
|
|
||||||
|
<h2>§7 Organe des Vereins</h2>
|
||||||
|
<p>Die Organe des Vereins sind:
|
||||||
|
<ul>
|
||||||
|
<li>die Mitgliederversammlung</li>
|
||||||
|
<li>der Vorstand</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>§8 Mitgliederversammlung</h2>
|
||||||
|
<p>Oberstes Beschlussorgan ist die Mitgliederversammlung. Ihrer Beschlussfassung unterliegen:
|
||||||
|
<ul>
|
||||||
|
<li>die Genehmigung des Finanzberichtes</li>
|
||||||
|
<li>die Entlastung des Vorstandes</li>
|
||||||
|
<li>die Wahl der einzelnen Vorstandsmitglieder</li>
|
||||||
|
<li>die Bestellung von FinanzprüferInnen</li>
|
||||||
|
<li>die Satzungsänderungen</li>
|
||||||
|
<li>die Genehmigung der Beitragsordnung</li>
|
||||||
|
<li>die Richtlinie über die Erstattung von Reisekosten und Auslagen</li>
|
||||||
|
<li>die Anträge des Vorstandes und der Mitglieder</li>
|
||||||
|
<li>die Ernennung von Ehrenmitgliedern</li>
|
||||||
|
<li>die Auflösung des Vereins</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Die ordentliche Mitgliederversammlung findet jedes Jahr beim Kongress der Kommenden Bastelei statt. </p>
|
||||||
|
|
||||||
|
<p>Mitgliederversammlungen können digital oder auch als Hybridveranstaltungen abgehalten werden. Technische
|
||||||
|
Hürden können durch Bildung von Kleingruppen gelöst werden.</p>
|
||||||
|
|
||||||
|
<p>Außerordentliche Mitgliederversammlungen werden auf Beschluss des Vorstandes abgehalten, wenn die Interessen des
|
||||||
|
Vereins dies erfordern, oder wenn mindestens fünf Prozent, bei weniger als 60 Mitgliedern mindestens drei
|
||||||
|
Mitglieder, aller stimmberechtigten Mitglieder dies unter Angabe des Zwecks in Textform beantragen. Die Einberufung
|
||||||
|
der Mitgliederversammlung erfolgt in Textform durch den Vorstand mit einer Frist von mindestens zwei Wochen. Zur
|
||||||
|
Wahrung der Frist reicht die Versendung an die zuletzt bekannte E-Mail-Adresse oder die Aufgabe der Einladung zur
|
||||||
|
Post an die letzte bekannte Anschrift. Hierbei sind die Tagesordnung bekannt zugeben und ihr die nötigen
|
||||||
|
Informationen zugänglich zu machen. Anträge zur Tagesordnung sind mindestens sieben Tage vor der
|
||||||
|
Mitgliederversammlung beim Vorstand in Textform einzureichen. Über die Behandlung von Initiativanträgen entscheidet
|
||||||
|
die Mitgliederversammlung.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Die Mitgliederversammlung ist beschlussfähig, wenn mindestens fünf Prozent, bei weniger als 60 Mitgliedern mindestens
|
||||||
|
drei Mitglieder, aller stimmberechtigten Mitglieder anwesend sind. Beschlüsse sind jedoch gültig, wenn die
|
||||||
|
Beschlussfähigkeit vor der
|
||||||
|
Beschlussfassung nicht angezweifelt worden ist. Ist die Mitgliederversammlung aufgrund mangelnder Teilnehmerzahl
|
||||||
|
nicht
|
||||||
|
beschlussfähig, ist die darauf folgende ordentlich einberufene Mitgliederversammlung ungeachtet der Teilnehmerzahl
|
||||||
|
beschlussfähig.</p>
|
||||||
|
|
||||||
|
<p>Beschlüsse über Satzungsänderungen und über die Auflösung des Vereins bedürfen zu ihrer Rechtswirksamkeit
|
||||||
|
der Dreiviertelmehrheit der anwesenden stimmberechtigten Mitglieder. In allen anderen Fällen genügt die
|
||||||
|
einfache Mehrheit.</p>
|
||||||
|
|
||||||
|
<p>Jedes stimmberechtigte Mitglied, welches mit den Beiträgen nicht im Rückstand ist, hat eine Stimme.
|
||||||
|
Stimmen können übertragen werden.</p>
|
||||||
|
|
||||||
|
<p>Über die Beschlüsse der Mitgliederversammlung ist ein Protokoll anzufertigen, das von der
|
||||||
|
VersammlungsleiterIn und der ProtokollführerIn zu unterzeichnen ist. Das Protokoll ist allen Mitgliedern
|
||||||
|
zugänglich zu machen und auf der nächsten Mitgliederversammlung genehmigen zu lassen.</p>
|
||||||
|
|
||||||
|
<p>Die Mitgliederversammlung wählt den Vorstand und die FinanzprüferInnen. Die Wahlen finden offen in Form
|
||||||
|
der „Wahl durch Zustimmung” statt.</p>
|
||||||
|
|
||||||
|
<p>Entsprechend sichere, elektronische Wahlformen sind zulässig, dadurch können jedoch keine geheimen Wahlen
|
||||||
|
durchgeführt werden. Abwesende Mitglieder können so jedoch auch an Wahlen teilnehmen. Technische Hürden
|
||||||
|
können durch Bevollmächtigungen gelöst werden.</p>
|
||||||
|
|
||||||
|
<p>Jede WählerIn kann beliebig vielen KandidatInnen jeweils eine Stimme geben. Jeder zu besetzende Posten
|
||||||
|
wird einzeln gewählt, wobei gleichrangige Posten (die zwei FinanzprüferInnen) jeweils gemeinsam gewählt
|
||||||
|
werden können. Bei der Wahl des Vorstandes ist gewählt, wer die meisten abgegebenen Stimmen erhält. Bei
|
||||||
|
Stimmengleichheit findet eine Stichwahl statt. Bei erneuter Stimmengleichheit entscheidet das Los. Bei
|
||||||
|
der Wahl der FinanzprüferInnen sind diejenigen beiden KandidatInnen gewählt, die die meisten Stimmen erhalten.
|
||||||
|
Bei Stimmengleichheit findet eine Stichwahl statt. Bei erneuter Stimmengleichheit entscheidet das Los.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>§9 Vorstand</h2>
|
||||||
|
<p>Der Vorstand besteht aus zwei oder mehr gleichberechtigten Mitgliedern.</p>
|
||||||
|
|
||||||
|
<p>Vorstand im Sinne des § 26, Abs. 2 BGB ist jedes Vorstandsmitglied. Ausgenommen sind Einstellung und
|
||||||
|
Entlassung von Angestellten, gerichtliche Vertretung sowie Anzeigen, Aufnahme von Krediten, Gründung,
|
||||||
|
Erwerb und Veräußerung von Gesellschaften und Geschäftsanteilen von Gesellschaften zur Verwirklichung
|
||||||
|
der satzungsgemäßen Ziele; bei denen der Verein durch mindestens zwei Vorstandsmitglieder vertreten
|
||||||
|
wird.</p>
|
||||||
|
|
||||||
|
<p>Scheidet ein Vorstandsmitglied vorzeitig aus, kann der Vorstand ein neues Vorstandsmitglied aus dem Kreis der
|
||||||
|
Mitglieder bis zur nächsten Vollversammlung berufen.</p>
|
||||||
|
|
||||||
|
<p>Die Amtsdauer der Vorstandsmitglieder beträgt zwei Jahre. Wiederwahl ist zulässig. Damit auch nach Ablauf
|
||||||
|
der Amtsdauer eine ordnungsgemäße gesetzliche Vertretung gesichert ist, bleibt der Vorstand bis zur
|
||||||
|
Neuwahl im Amt.</p>
|
||||||
|
|
||||||
|
<p>Der Vorstand ist Dienstvorgesetzter aller vom Verein angestellten MitarbeiterInnen.</p>
|
||||||
|
|
||||||
|
<p>Die Vorstandsmitglieder nehmen eine interne Aufgabenverteilung vor. Mit dem Ablauf des Geschäftsjahres
|
||||||
|
stellt der Vorstand unverzüglich die Abrechnung sowie die Vermögensübersicht und sonstige Unterlagen von
|
||||||
|
wirtschaftlichen Belang den FinanzprüferInnen des Vereins zur Prüfung zur Verfügung.</p>
|
||||||
|
|
||||||
|
<p>Der Vorstand führt die laufenden Geschäfte des Vereins. Bei der Geschäftsführung sind die
|
||||||
|
Vorstandsmitglieder an die Beschlüsse der Mitgliederversammlung gebunden. Der Vorstand soll seine
|
||||||
|
gesamte Tätigkeit so durchschaubar wie möglich erledigen und andere Vereinsmitglieder kooperativ
|
||||||
|
beteiligen. Der Vorstand kann haupt- oder ehrenamtlich Tätige mit der Führung der Geschäfte beauftragen.</p>
|
||||||
|
|
||||||
|
<p>Der Vorstand verwaltet das Vereinsvermögen.</p>
|
||||||
|
|
||||||
|
<p>Der Verein wird gerichtlich und außergerichtlich durch zwei Vorstandsmitglieder gemeinsam vertreten.</p>
|
||||||
|
|
||||||
|
<h2>§10 FinanzprüferInnen</h2>
|
||||||
|
<p>Zur Kontrolle der Haushaltsführung bestellt die Mitgliederversammlung zwei FinanzprüferInnen. Nach
|
||||||
|
Durchführung ihrer Prüfung informieren sie den Vorstand von ihrem Prüfungsergebnis und erstatten der
|
||||||
|
Mitgliederversammlung Bericht.</p>
|
||||||
|
|
||||||
|
<p>Die FinanzprüferInnen dürfen dem Vorstand nicht angehören.</p>
|
||||||
|
|
||||||
|
<p>Die FinanzprüferInnen sind grundsätzlich ehrenamtlich tätig; sie haben Anspruch auf Erstattung
|
||||||
|
notwendiger Auslagen im Rahmen einer von der Mitgliederversammlung zu beschließenden Richtlinie über die
|
||||||
|
Erstattung von Reisekosten und Auslagen.</p>
|
||||||
|
|
||||||
|
<h2>§11 Auflösung des Vereins</h2>
|
||||||
|
|
||||||
|
<p>Bei der Auflösung des Vereins oder bei Wegfall seines Zweckes fällt das Vereinsvermögen an eine von der
|
||||||
|
Mitgliederversammlung zu bestimmende Körperschaft des öffentlichen Rechts oder eine andere
|
||||||
|
steuerbegünstigte Körperschaft zwecks Verwendung für die bürgerschaftliche Bildung.</p>
|
7
src/assets/templates/de-informal/club/membership.html
Normal file
7
src/assets/templates/de-informal/club/membership.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<p>Die Vereinsmitgliedschaft richtet sich an alle, die den Verein aktiv unterstützen und mitgestalten möchten. Als Vereinsmitglied kannst du durch Teilnahme an Mitgliederversammlungen und Abstimmungen mitbestimmen.</p>
|
||||||
|
|
||||||
|
<p>Vor allem mit Blick auf zukünftige Projekte auch außerhalb des digitalen Raumes ist deine aktive Beteiligung nötig, damit wir größere Vielfältigkeit und Kreativität in unsere Projekte bekommen. Und da wir große Freude an demokratischen Prozessen haben, ist jedes Mitglied auch eine Bereicherung um Vereinsprozesse offener gestalten zu können. Bei der Bastelei soll es um möglichst kreative, zwanglose und gute Zusammenarbeit zur Bereicherung aller Beteiligten gehen.</p>
|
||||||
|
|
||||||
|
<p>Wenn du also nicht nur ein Interesse an unseren digitalen Services hast, sondern Ideen und Ziele mitverfolgen möchtest, kannst du dir deine Vereinsmitgliedschaft ganz einfach unter <a href="https://we.bstly.de/" target="_blank">we.bstly.de</a> klicken. Du musst nur noch auf Genehmigung deines Antrages warten und anschließend deinen ersten Mitgliedsbeitrag überweisen. Anschließend bekommst du jährlich automatisch eine Erinnerung an deine hinterlegte E-Mail Adresse, deine Mitgliedschaft zu verlängern.</p>
|
||||||
|
|
||||||
|
<p class="hint">Falls du nur an unseren digitalen Angeboten interessiert bist, bieten wir diese auch Nicht-Vereinsmitgliedern an. Du solltest allerdings grundsätzlich mit den Zielen und Idealen des Vereins übereinstimmen. Wir nutzen die Beiträge aus deiner Nutzung, um unsere Vereinsarbeit weiter zu finanzieren.</p>
|
4
src/assets/templates/de-informal/general/we.html
Normal file
4
src/assets/templates/de-informal/general/we.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<p>Jeden Monat überweisen wir zwielichtigen Konzernen zweistellige Beträge, damit sie uns unsere Daten abnehmen. Dafür
|
||||||
|
bekommen wir intransparente Software mit denen wir auf Geräten, die wir nicht kontrollieren irgendwie unseren
|
||||||
|
digitalen Alltag bestreiten müssen. Als erste Initiative dem ein Ende zu setzen bringen _Bastler
|
||||||
|
und Louis Fabu, der Sekretär der Kommenden Bastelei, euch DIE DIGITALE BASTELEI.</p>
|
11
src/assets/templates/de-informal/general/what.html
Normal file
11
src/assets/templates/de-informal/general/what.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<p>Wir betreiben freie Software auf selbst-verwaltenden Servern und bieten euch so Alternativen zu den sog.
|
||||||
|
"Cloud-Diensten", die euch sonst Google, Apple oder wer sonst andrehen. Wir verteilen die Kosten gleichmäßig auf
|
||||||
|
alle Nutzenden und sammeln noch etwas mehr Geld ein, um das Ganze weiter zu entwickeln und weitere Projekte und
|
||||||
|
Initiativen im Rahmen der Kommenden Bastelei zu finanzieren. Wir verwahren nur <a href="/privacy">minimale Daten</a>
|
||||||
|
von euch und
|
||||||
|
verknüpfen eure Zahlungsinformationen nicht mit euren User-Konten.</p>
|
||||||
|
<p>
|
||||||
|
Zum Start gibt es aktuell für 3€ im Monat zunächst ein E-Mail-Konto auf @bstly.de mit eigenem Adressbereich, 5GB
|
||||||
|
Speicher und catch-all-Funktion und einen Account für unsere Nextcloud mit 15GB
|
||||||
|
Speicherplatz. Dazu gehören so praktische Funktionen wie synchronisierbare Kalender, Online Office zum
|
||||||
|
kollaborativen Erstellen von Texten und einiges mehr. Mehr Details zu den aktuellen und geplanten Services findest du <a href="/services">hier</a>.</p>
|
7
src/assets/templates/de-informal/general/you.html
Normal file
7
src/assets/templates/de-informal/general/you.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<p>Du kannst Teil der Digitalen Bastelei werden: Entweder wirst du Vereinsmitglied oder bezahlst deine Teilnahme direkt
|
||||||
|
über unser
|
||||||
|
Einkaufs-System und überweist uns im halbjährlichen oder
|
||||||
|
jährlichen
|
||||||
|
Abstand. Einen Invite bekommst du bei den Beauftragten der Kommenden Bastelei in eurer Nähe. Je mehr Leute die Services
|
||||||
|
nutzen, desto effizienter wird es natürlich, weswegen wir später eventuell weitere Dienste, Upgrades oder
|
||||||
|
Wunschfeatures zur Verfügung stellen können.</p>
|
51
src/assets/templates/de-informal/imprint.html
Normal file
51
src/assets/templates/de-informal/imprint.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<h2>Impressum</h2>
|
||||||
|
|
||||||
|
<p>Kontakt<br>
|
||||||
|
Name: Bastelei (bald e. V.)<br>
|
||||||
|
E-Mail: impressum@bstly.de</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Ausschluss der Haftung</h3>
|
||||||
|
|
||||||
|
<h4>1. Haftung für Inhalte</h4>
|
||||||
|
|
||||||
|
<p>Der Inhalt unserer Internetseiten wurde mit größtmöglicher Sorgfalt erstellt. Wir übernehmen jedoch keine Gewähr
|
||||||
|
dafür,
|
||||||
|
dass dieser Inhalt richtig, vollständig, und aktuell ist und zudem noch gefällt. Gemäß § 7 Abs. 1 TMG sind wir für
|
||||||
|
den Inhalt verantwortlich, selbst wenn dieser bestellt wurde.</p>
|
||||||
|
|
||||||
|
<p>Gemäß den §§ 8, 9 und 10 TMG ist für uns keine Verpflichtung gegeben, dass wir Informationen von Dritten, die
|
||||||
|
übermittelt oder gespeichert wurden, überwachen oder Umstände erheben, die Hinweise auf nicht rechtmäßige
|
||||||
|
Tätigkeiten
|
||||||
|
ergeben.</p>
|
||||||
|
|
||||||
|
<p>Davon nicht berührt, ist unsere Verpflichtung zur Sperrung oder Entfernung von Informationen, welche von den
|
||||||
|
allgemeinen Gesetzen herrührt.</p>
|
||||||
|
|
||||||
|
<p>Wir haften allerdings erst in dem Moment, in dem wir von einer konkreten Verletzung von Rechten Kenntnis bekommen.
|
||||||
|
Dann
|
||||||
|
wird eine unverzügliche Entfernung des entsprechenden Inhalts vorgenommen.</p>
|
||||||
|
|
||||||
|
<h4>2. Haftung für Links</h4>
|
||||||
|
|
||||||
|
<p>Unsere Internetseiten enthält Links, die zu externen Internetseiten von Dritten führen, auf deren Inhalte wir jedoch
|
||||||
|
keinen Einfluss haben. Es ist uns daher nicht möglich, eine Gewähr für diese Inhalte zu tragen.</p>
|
||||||
|
|
||||||
|
<p>Die Verantwortung dafür hat immer der jeweilige Anbieter/Betreiber der entsprechenden Internetseite. Wir überprüfen
|
||||||
|
die von uns verlinkten Internetseiten zum Zeitpunkt der Verlinkung auf einen möglichen Rechtsverstoß in voller
|
||||||
|
Breite.</p>
|
||||||
|
|
||||||
|
<p>Es kann uns jedoch, ohne einen konkreten Anhaltspunkt, nicht zugemutet werden, ständig die verlinkten Internetseiten
|
||||||
|
inhaltlich zu überwachen. Wenn wir jedoch von einer Rechtsverletzung Kenntnis erlangen, werden wir den
|
||||||
|
entsprechenden
|
||||||
|
Link unverzüglich entfernen.</p>
|
||||||
|
|
||||||
|
<h4>3. Urheberrecht</h4>
|
||||||
|
|
||||||
|
<p>Wir weisen darauf hin, dass wir hinsichtlich der Inhalte auf unserer Internetseiten, soweit sie nicht von uns
|
||||||
|
erstellt
|
||||||
|
worden sind, das Urheberrecht von Dritten jederzeit beachtet haben.</p>
|
||||||
|
|
||||||
|
<p>Wenn du uns mitteilen würdest, dass du trotzdem eine Urheberrechtsverletzung gefunden hast, würden wir das sehr
|
||||||
|
schätzen. Dann können wir den entsprechenden Inhalt sofort entfernen und würde damit das Urheberrecht nicht mehr
|
||||||
|
verletzen.</p>
|
384
src/assets/templates/de-informal/privacy-policy.html
Normal file
384
src/assets/templates/de-informal/privacy-policy.html
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
<p>Die folgende Erklärung gilt für die Domain bstly.de sowie deren Subdomains.</p>
|
||||||
|
<h2>Datenschutzerklärung</h2>
|
||||||
|
|
||||||
|
<p>Personenbezogene Daten (nachfolgend zumeist nur „Daten“ genannt) werden von uns nur im Rahmen der Erforderlichkeit
|
||||||
|
sowie zum Zwecke der Bereitstellung eines funktionsfähigen und nutzerfreundlichen Internetauftritts, inklusive
|
||||||
|
seiner Inhalte und der dort angebotenen Leistungen, verarbeitet.</p>
|
||||||
|
|
||||||
|
<p>Gemäß Art. 4 Ziffer 1. der Verordnung (EU) 2016/679, also der Datenschutz-Grundverordnung (nachfolgend nur „DSGVO“
|
||||||
|
genannt), gilt als „Verarbeitung“ jeder mit oder ohne Hilfe automatisierter Verfahren ausgeführter Vorgang oder jede
|
||||||
|
solche Vorgangsreihe im Zusammenhang mit personenbezogenen Daten, wie das Erheben, das Erfassen, die Organisation,
|
||||||
|
das Ordnen, die Speicherung, die Anpassung oder Veränderung, das Auslesen, das Abfragen, die Verwendung, die
|
||||||
|
Offenlegung durch Übermittlung, Verbreitung oder eine andere Form der Bereitstellung, den Abgleich oder die
|
||||||
|
Verknüpfung, die Einschränkung, das Löschen oder die Vernichtung.</p>
|
||||||
|
|
||||||
|
<p>Mit der nachfolgenden Datenschutzerklärung informieren wir dich insbesondere über Art, Umfang, Zweck, Dauer und
|
||||||
|
Rechtsgrundlage der Verarbeitung personenbezogener Daten, soweit wir entweder allein oder gemeinsam mit anderen über
|
||||||
|
die Zwecke und Mittel der Verarbeitung entscheiden.</p>
|
||||||
|
|
||||||
|
<p>Die Datenschutzerklärung ist wie folgt gegliedert:<br>
|
||||||
|
I. Informationen über uns als Verantwortliche<br>
|
||||||
|
II. Rechte der Nutzer und Betroffenen<br>
|
||||||
|
III. Informationen zur Datenverarbeitung</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>I. Informationen über uns als Verantwortlicher</h3>
|
||||||
|
|
||||||
|
<p>Verantwortliche Anbieter dieses Internetauftritts im datenschutzrechtlichen Sinne:<br>
|
||||||
|
Name: Bastelei (bald e. V.)<br>
|
||||||
|
E-Mail: datenschutz@bstly.de</p>
|
||||||
|
|
||||||
|
<h3>II. Rechte der Nutzer und Betroffenen</h3>
|
||||||
|
|
||||||
|
<p>Mit Blick auf die nachfolgend noch näher beschriebene Datenverarbeitung hast du als Nutzer und Betroffenen das Recht
|
||||||
|
auf Bestätigung, ob dich betreffende Daten verarbeitet werden, auf Auskunft über die verarbeiteten Daten, auf
|
||||||
|
weitere
|
||||||
|
Informationen über die Datenverarbeitung sowie auf Kopien der Daten (vgl. auch Art. 15 DSGVO);
|
||||||
|
auf Berichtigung oder Vervollständigung unrichtiger bzw. unvollständiger Daten (vgl. auch Art. 16 DSGVO);
|
||||||
|
auf unverzügliche Löschung der dich betreffenden Daten (vgl. auch Art. 17 DSGVO), oder, alternativ, soweit eine
|
||||||
|
weitere Verarbeitung gemäß Art. 17 Abs. 3 DSGVO erforderlich ist, auf Einschränkung der Verarbeitung nach Maßgabe
|
||||||
|
von Art. 18 DSGVO;
|
||||||
|
auf Erhalt der dich betreffenden und von dir bereitgestellten Daten und auf Übermittlung dieser Daten an andere
|
||||||
|
Anbieter/Verantwortliche (vgl. auch Art. 20 DSGVO);<br>
|
||||||
|
auf Beschwerde gegenüber der Aufsichtsbehörde, sofern du der Ansicht bist, dass die dich betreffenden Daten durch
|
||||||
|
den Anbieter unter Verstoß gegen datenschutzrechtliche Bestimmungen verarbeitet werden (vgl. auch Art. 77 DSGVO).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Darüber hinaus ist der Anbieter dazu verpflichtet, alle Empfänger, denen gegenüber Daten durch den Anbieter
|
||||||
|
offengelegt worden sind, über jedwede Berichtigung oder Löschung von Daten oder die Einschränkung der Verarbeitung,
|
||||||
|
die aufgrund der Artikel 16, 17 Abs. 1, 18 DSGVO erfolgt, zu unterrichten. Diese Verpflichtung besteht jedoch nicht,
|
||||||
|
soweit diese Mitteilung unmöglich oder mit einem unverhältnismäßigen Aufwand verbunden ist. Unbeschadet dessen hat
|
||||||
|
der Nutzer ein Recht auf Auskunft über diese Empfänger.</p>
|
||||||
|
|
||||||
|
<p>Ebenfalls hast du als Nutzer und Betroffenen nach Art. 21 DSGVO das Recht auf Widerspruch gegen die künftige
|
||||||
|
Verarbeitung der dich betreffenden Daten, sofern die Daten durch den Anbieter nach Maßgabe von Art. 6 Abs. 1 lit. f)
|
||||||
|
DSGVO verarbeitet werden. Insbesondere ist ein Widerspruch gegen die Datenverarbeitung zum Zwecke der Direktwerbung
|
||||||
|
statthaft.</p>
|
||||||
|
|
||||||
|
<p>Du hast gemäß denVorschriften der Datenschutzgrundverordnung (DSGVO) ein Auskunftsrecht über die zu deiner Person
|
||||||
|
gespeicherten Daten, einen Berichtigungsanspruch sowie – bei Vorliegen der rechtlichen Voraussetzungen – einen
|
||||||
|
Anspruch auf Einschränkung der Verarbeitung und Löschung.</p>
|
||||||
|
|
||||||
|
<p>Eine Auskunft / Löschung kann entweder in den entsprechenden Diensten über die persönlichen Einstellungen angefordert
|
||||||
|
werden oder per E-Mail Kontakt erfragt werden.</p>
|
||||||
|
|
||||||
|
<h3>III. Informationen zur Datenverarbeitung</h3>
|
||||||
|
|
||||||
|
<p>Deine bei Nutzung des Internetauftritts verarbeiteten Daten werden gelöscht oder gesperrt, sobald der Zweck der
|
||||||
|
Speicherung entfällt, der Löschung der Daten keine gesetzlichen Aufbewahrungspflichten entgegenstehen und
|
||||||
|
nachfolgend keine anderslautenden Angaben zu einzelnen Verarbeitungsverfahren gemacht werden.</p>
|
||||||
|
|
||||||
|
<h4>Serverdaten</h4>
|
||||||
|
|
||||||
|
<p>Aus technischen Gründen, werden Daten durch deinen Internet-Browser an den Server übermittelt. Soweit technisch
|
||||||
|
möglich, werden Daten wie u.a. Typ und Version deines Internetbrowsers, das Betriebssystem, die Website, von der aus
|
||||||
|
du auf unseren Internetauftritt gewechselt hast (Referrer URL), die Website(s) des Internetauftritts, die du
|
||||||
|
besuchst, Datum und Uhrzeit des jeweiligen Zugriffs sowie die IP-Adresse des Internetanschlusses, von dem aus die
|
||||||
|
Nutzung unseres Internetauftritts erfolgt, nicht(!) erhoben.</p>
|
||||||
|
|
||||||
|
<p>Da unser Interesse im Schutz dieser personenbezogenen Daten liegt, werden diese Daten generell nicht erhoben. Zur
|
||||||
|
Verbesserung, Stabilität, Funktionalität und Sicherheit unseres Internetauftritts ist jedoch eine temporäre
|
||||||
|
Erhebung möglich. Diese Speicherung erfolgt auf der Rechtsgrundlage von Art. 6 Abs. 1 lit. f) DSGVO.</p>
|
||||||
|
|
||||||
|
<p>Sollten diese so erhobenen Daten vorübergehend in sog. Server-Log-Files gespeichert werden, geschieht dies jedoch
|
||||||
|
nicht gemeinsam mit anderen Daten von dir.</p>
|
||||||
|
|
||||||
|
<p>Die Daten werden spätestens nach 14 Tagen wieder gelöscht.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Kontaktanfragen / Kontaktmöglichkeit</h4>
|
||||||
|
|
||||||
|
<p>Sofern Du per Kontaktformular oder E-Mail mit uns in Kontakt trittst, werden die dabei von dir angegebenen Daten
|
||||||
|
zur Bearbeitung deiner Anfrage genutzt. Die Angabe der Daten ist zur Bearbeitung und Beantwortung deiner Anfrage
|
||||||
|
erforderlich - ohne deren Bereitstellung können wir deine Anfrage nicht oder allenfalls eingeschränkt beantworten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Rechtsgrundlage für die Verarbeitung dieser Daten ist Art. 6 Abs. 1 lit. b) DSGVO.</p>
|
||||||
|
|
||||||
|
<p>Deine Daten werden gelöscht, sofern deine Anfrage abschließend beantwortet worden ist und der Löschung keine
|
||||||
|
gesetzlichen Aufbewahrungspflichten entgegenstehen, wie bspw. bei einer sich etwaig anschließenden
|
||||||
|
Vertragsabwicklung.</p>
|
||||||
|
|
||||||
|
<h4>Nutzung des Dienstes Pretix</h4>
|
||||||
|
|
||||||
|
<h5>Gespeicherte Daten</h5>
|
||||||
|
|
||||||
|
<p>Die folgenden Daten werden durch den Dienst Pretix erfasst und gespeichert:
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th>Betroffene Benutzer / Speicherfrist</th>
|
||||||
|
<th>Verwendungszweck / Weitergabe an Dritte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Cookies</strong>
|
||||||
|
<p>Zufällig generierte IDs, technisch bedingte Parameter</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>alle Besucher der Seite / Sitzungsende (Beenden des Browsers)</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Wiedererkennung des Benutzers während der Nutzung der Anwendung</p>
|
||||||
|
<p>keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Rechnungsdaten / Mitgliedsdaten</strong>
|
||||||
|
<p>E-Mail Adresse, Name, Anschrift (freiwillig: Kommentar, Referenz)</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit getätigter Bestellung / bis zur Löschung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Durchführung der Bestellung, interne Auflistung der Vereinsmitglieder, Archivierung für Steuerprüfung
|
||||||
|
</p>
|
||||||
|
<p>keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Bestelldaten</strong>
|
||||||
|
<p>Datum, Status, Menge sowie Art der Bestellung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit getätigter Bestellung / bis zur Löschung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Durchführung der Bestellung, automatische Erinnerung für Mitglieder</p>
|
||||||
|
<p>Keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Nutzung des Dienstes we.bstly</h4>
|
||||||
|
|
||||||
|
<h5>Gespeicherte Daten</h5>
|
||||||
|
|
||||||
|
<p>Die folgenden Daten werden durch den Dienst we.bstly erfasst und gespeichert:
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th>Betroffene Benutzer / Speicherfrist</th>
|
||||||
|
<th>Verwendungszweck / Weitergabe an Dritte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Cookies</strong>
|
||||||
|
<p>Zufällig generierte IDs, technisch bedingte Parameter</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>alle Besucher der Seite / Sitzungsende (Beenden des Browsers)</p>
|
||||||
|
<p>Benutzer der „automatischen Anmeldung“ / bis zur aktiven Beendigung der Sitzung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Wiedererkennung des Benutzers während der Nutzung der Anwendung</p>
|
||||||
|
<p>Wiedererkennung des Benutzers bei „automatischer Anmeldung“</p>
|
||||||
|
<p>keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Account-Daten</strong>
|
||||||
|
<p>Benutzername, öffentlicher PGP Schlüssel (freiwillig: E-Mail Adresse)</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / bis zur Löschung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Identifizierung für Login, Nutzung weiterer Diensten</p>
|
||||||
|
<p>keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p>Passwort</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Keine permanente Speicherung, direkte Weitergabe an Authentifizierungsserver</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Authentifizierung (Login)</p>
|
||||||
|
<p>Keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Einstellungen / Eigenschaften</strong>
|
||||||
|
<p>Berechtigungen und Quotas (freiwillig: Parameter Zweifaktor Authentifizierung)</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / siehe Account-Daten</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Nutzung weiterer Diensten, Steuerung des Login Prozesses</p>
|
||||||
|
<p>Keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Nutzung des Dienstes Nextcloud</h4>
|
||||||
|
|
||||||
|
<h5>Gespeicherte Daten</h5>
|
||||||
|
|
||||||
|
<p>Die folgenden Daten werden durch den Dienst Nextcloud erfasst und gespeichert:
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th>Betroffene Benutzer / Speicherfrist</th>
|
||||||
|
<th>Verwendungszweck / Weitergabe an Dritte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Cookies</strong>
|
||||||
|
<p>Zufällig generierte IDs, technisch bedingte Parameter</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>alle Besucher der Seite / Sitzungsende (Beenden des Browsers)</p>
|
||||||
|
<p>Benutzer der „automatischen Anmeldung“ / 16 Tage nach letzter Nutzung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Wiedererkennung des Benutzers während der Nutzung der Anwendung</p>
|
||||||
|
<p>Wiedererkennung des Benutzers bei „automatischer Anmeldung“</p>
|
||||||
|
<p>keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Account-Daten</strong>
|
||||||
|
<p>Benutzername (freiwillig: E-Mail Adresse)</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / bis zur Löschung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Suche nach Benutzern beim Teilen von Inhalten, Senden von Benachrichtigungen</p>
|
||||||
|
<p>Weitergabe an alle Nutzer</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Einstellungen/ Eigenschaften</strong>
|
||||||
|
<p>Zeitstempel letztes Login, Speicherplatzkontingent, Speicherplatzkauf / Laufzeit, Sprache,
|
||||||
|
vorgenommene persönliche Einstellungen</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / siehe Account-Daten</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Erkennung inaktiver Benutzer, Speicherplatzzuweisung, persönliche Anpassung der Oberfläche,
|
||||||
|
Benachrichtigungen, etc.</p>
|
||||||
|
<p>Keine Weitergabe an Dritte</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Aktivitäten</strong>
|
||||||
|
<p>Auflistung der im System durchgeführten Aktionen, z.B. Upload von Dateien</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / 14 Tage</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Nachvollziehbarkeit von Änderungen</p>
|
||||||
|
<p>Weitergabe individuell vom Nutzer einstellbar</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>strukturierte Daten</strong>
|
||||||
|
<p>Daten, welche von Apps in der Datenbank abgelegt werden</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / maximale Speicherfrist siehe Account-Daten, ansonsten abhängig von der
|
||||||
|
jeweiligen App</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Nutzung der jeweiligen Apps</p>
|
||||||
|
<p>Weitergabe von in Apps erfassten Daten individuell vom Nutzer einstellbar</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Dateien</strong>
|
||||||
|
<p>Dateien, welche mit der „Dateien“-Anwendung oder externen Clients abgelegt werden</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit Account / siehe Account-Daten</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Nutzung der „Dateien“-App oder Clients für verschiedene Plattformen zur Dateisynchronisierung, Teilen
|
||||||
|
von Dateien mit Dritten</p>
|
||||||
|
<p>Weitergabe von Dateien individuell vom Nutzer einstellbar</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Nutzung des Dienstes E-Mail Postfach</h4>
|
||||||
|
|
||||||
|
|
||||||
|
<h5>Gespeicherte Daten</h5>
|
||||||
|
|
||||||
|
<p>Die folgenden Daten werden durch den Dienst E-Mail Postfach erfasst und gespeichert:
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th>Betroffene Benutzer / Speicherfrist</th>
|
||||||
|
<th>Verwendungszweck / Weitergabe an Dritte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>E-Mails</strong>
|
||||||
|
<p>Empfangene und gesendete E-Mails</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Benutzer mit E-Mail Postfach / bis zur Löschung</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Speicherung der E-Mails zur Abfrage</p>
|
||||||
|
<p>Jeweilige Sender & Empfänger der E-Mail</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>Sicherheit</h4>
|
||||||
|
|
||||||
|
<p>Sämtliche Daten werden verschlüsselt übertragen. Die Nutzung einer unverschlüsselten Verbindung zum Server ist
|
||||||
|
technisch ausgeschlossen.</p>
|
||||||
|
|
||||||
|
<p>Zusätzliche Sicherheitsfunktionen, wie 2-Faktor-Authentifizierung und anwendungsspezifische Logins werden unterstützt
|
||||||
|
und können in den Einstellungen aktiviert werden.</p>
|
||||||
|
|
||||||
|
<h4>Rechenzentrum</h4>
|
||||||
|
|
||||||
|
<p>Die Daten werden im Rechenzentrum der <a href="https://www.netcup.de/ueber-netcup/rechenzentrum.php"
|
||||||
|
target="_blank">netcup GmbH</a> gespeichert. Eine regelmäßige, automatisierte Datensicherung der Bestandsdaten wird durchgeführt.
|
||||||
|
</p>
|
25
src/assets/templates/de-informal/privacy/design.html
Normal file
25
src/assets/templates/de-informal/privacy/design.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<p>Hier findest du eine kleine Übersicht über Datenschutz bei uns und unser Verständnis davon. Die genaue Auflistung der
|
||||||
|
Verwendung deiner Nutzungsdaten findest du in unserer <a href="/privacy-policy">Datenschutzerklärung</a>.</p>
|
||||||
|
|
||||||
|
<p>Privacy by Design bedeutet, dass die Systeme grundlegend auf Datensparsamkeit ausgelegt sind. Wir versuchen generell immer so
|
||||||
|
wenig Daten wie möglich zu erheben. Frei nach dem Motto 'was man nicht hat, kann man auch nicht verlieren'. Das gilt
|
||||||
|
zuerst einmal generell für Daten, die wir erheben, aber auch für die Verknüpfung verschiedener Daten zueinander.</p>
|
||||||
|
|
||||||
|
<p>Um Account- und Zahlungsdaten zu trennen, benutzen wir das Ticket-System Pretix mit seinem Check-In System. Für jedes
|
||||||
|
Item wird ein geheimes Token generiert. In unserer selbst entwickelten Serverkomponente, we.bstly, können dann diese
|
||||||
|
Tokens eingelöst werden und die entsprechende Leistung gespeichert werden. Das Token wird dann in Pretix als
|
||||||
|
eingelöst markiert.</p>
|
||||||
|
|
||||||
|
<p>Die Verknüpfung von Account/Leistung und Token ist also nur temporär für die bestehende Browser Session gültig und
|
||||||
|
wird sonst nicht dauerhaft gespeichert. Sprich: es gibt zwei Datenbanken, eine für Zahlungsdaten und Tokens
|
||||||
|
(Pretix-System) und eine mit Account- und Berechtigungsdaten (we.bstly-System). Die Verknüpfung findet nur in einer
|
||||||
|
aktiven Browser-Session statt und wird nach Einlösen des Tokens auch dort vergessen. Es gibt also keinerlei
|
||||||
|
Verknüpfung von Zahlungsdaten und Account.</p>
|
||||||
|
|
||||||
|
<p>Abstriche machen wir aktuell bei der Verknüpfung von Accounts bei den einzelnen Services. Wie immer haben Komfort und
|
||||||
|
Einfachheit ihren Preis. Durch Single-Sign-On (SSO) über OIDC heißt das: Ein Account für Alles. Sprich, die Nutzung
|
||||||
|
der einzelnen Services ist immer auf deinen we.bstly-Account zurückzuführen. Zum Einen bietet das den Komfort von
|
||||||
|
SSO, dass du nur auf diesen einen Account gut aufpassen musst. Zum Anderen bekommt man dafür natürlich auch die
|
||||||
|
Einfachheit, dass alle die Sicherheit haben, dass es sich bei den verschiedenen Services immer um den gleichen
|
||||||
|
we.bstly-Account handelt. So weißt du z.B., dass du auch genau dem Menschen eine E-Mail schreibst, mit dem du gerade
|
||||||
|
gechattest hast.</p>
|
8
src/assets/templates/de-informal/privacy/email.html
Normal file
8
src/assets/templates/de-informal/privacy/email.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<p>Aktuell werden die E-Mails so wie sie ankommen auf dem Server gespeichert. Da dies einige Nachteile und unnötiges
|
||||||
|
Vertrauen benötigt, arbeitet _Bastler an einer Lösung, dass alle E-Mails automatisch mit deinem Public Key
|
||||||
|
verschlüsselt werden. Das gibt dir die Sicherheit, dass auch nur du die E-Mails entschlüsseln kannst. Allerdings
|
||||||
|
bedeutet dies auch, dass du all deine E-Mail-Clients für die Entschlüsselung einrichten musst. Wir werden
|
||||||
|
selbstverständlich detaillierte Anleitungen dazu veröffentlichen wenn es soweit ist und vermutlich auch ein Opt-Out
|
||||||
|
anbieten, wenn du auf diese Funktion verzichten möchtest.</p>
|
||||||
|
|
||||||
|
<p>Zur Authentifizierung werden deine we.bstly Account-Daten verwendet, d.h. hier gilt immer dieselbe Sicherheit.</p>
|
14
src/assets/templates/de-informal/privacy/nextcloud.html
Normal file
14
src/assets/templates/de-informal/privacy/nextcloud.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<p>Wie sensibel die Daten in der Nextcloud sind, hängt natürlich nur von deiner Nutzung ab. Vom Dienst selber fallen
|
||||||
|
keine Daten außer deine Account-Daten (nur Username und freiwillige Profildaten) an. Da hier ausschließlich von
|
||||||
|
Usern
|
||||||
|
selbst erstellte Inhalte gespeichert werden, kommt es darauf an, was du hochlädst, veröffentlichst und schreibst.
|
||||||
|
Die Daten werden automatisch verschlüsselt gespeichert, es handelt sich
|
||||||
|
dabei allerdings nur um eine serverseitige Verschlüsselung, sodass du die Daten weiterhin mit Anderen teilen kannst.
|
||||||
|
Nextcloud bietet allerdings in den aktuellen Versionen auch eine eigene Ende-Zu-Ende-Verschlüsselung (E2EE) an. Es
|
||||||
|
steht dir natürlich frei diese für sensible Daten zu nutzen, sodass auch niemand anderes an diese Dateien kommt.
|
||||||
|
Beachte aber, dass diese Dateien dann nicht mehr im Browser zugänglich sind und nicht geteilt werden können.
|
||||||
|
Außerdem gilt die E2EE nur für Dateien und nicht für andere Daten wie Nachrichten, Kalender o.Ä., so dass die
|
||||||
|
Empfehlung ist, immer darüber nachzudenken, welche Daten man gerade erzeugt und wie sensibel diese sind.</p>
|
||||||
|
|
||||||
|
<p>Da die Nextcloud auch als Basis für unsere Community dient, werden deine Account-Daten mit allen anderen Usern
|
||||||
|
geteilt. Es steht dir aber auch frei, weitere Daten wie Dateien, Kalender etc. mit anderen Usern zu teilen.</p>
|
3
src/assets/templates/de-informal/privacy/pretix.html
Normal file
3
src/assets/templates/de-informal/privacy/pretix.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<p>Im Pretix System müssen wir natürlich persönliche Daten zum Zahlungsverkehr sowie eine E-Mail-Adresse speichern zum
|
||||||
|
Versenden von E-Mails mit Bestätigungen, Zahlungsdaten sowie zum Verschicken der Tokens. Als Vereinsmitglied werden
|
||||||
|
hier deine zu erhebenden Mitgliedsdaten sowie dein Mitgliedsbeitragskonto gespeichert.</p>
|
12
src/assets/templates/de-informal/privacy/we-bstly.html
Normal file
12
src/assets/templates/de-informal/privacy/we-bstly.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<p>Im we.bstly-System brauchst du lediglich einen Usernamen und ein Passwort (gut gesalzen, Argon2 gehashed!).
|
||||||
|
Zusätzlich wird noch ein Private-Public-Schlüsselpaar erstellt. Mehr nicht. Optional ist noch die Angabe einer
|
||||||
|
E-Mail Adresse. Diese ist erforderlich beim Verlust der Login-Daten bzw. deines Private-Keys. Wir halten diese
|
||||||
|
Option allerdings offen, so dass eine völlig anonyme Nutzung aller Dienste möglich ist, wenn du dein Passwort bzw.
|
||||||
|
deinen Private-Key nicht verlierst!</p>
|
||||||
|
|
||||||
|
<p>Zusätzlich zu deinen zentralen Account-Daten werden hier auch deine Berechtigungen und das Ablaufdatum deiner Services
|
||||||
|
gespeichert. Aktuell ist es auch vorgesehen, die Information zu speichern, ob du ein reguläres Vereinsmitglied bist.
|
||||||
|
Dies hat den Vorteil, dass wir über unsere Services auch alle Vereinsmitglieder direkt erreichen können oder
|
||||||
|
erweiterte Services anbieten können. Die Daten werden selbstverständlich nicht mit deinen Mitgliedsdaten und dem
|
||||||
|
Mitgliedsbeitragskonto verknüpft, sprich: wir wissen lediglich, <strong>dass</strong> du Vereinsmitglied bist,
|
||||||
|
<strong>nicht welches</strong>!</p>
|
9
src/assets/templates/de-informal/privacy/webserver.html
Normal file
9
src/assets/templates/de-informal/privacy/webserver.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<p>Alle Dienste laufen über einen Webserver, wie auch z.B. diese Seite. Generell fallen bei einem Webserver automatisch
|
||||||
|
ein Haufen Daten an, wie z.B. deine IP-Adresse, deine Webbrowser-Version und einiges mehr. Dass diese Daten
|
||||||
|
übertragen werden ist technisch bedingt und lässt sich nur mit Aufwand von deiner Seite aus verhindern. Wichtig ist
|
||||||
|
also, was wir mit diesen Daten machen.</p>
|
||||||
|
|
||||||
|
<p>Und was machen wir mit diesen Daten? Die Antwort ist einfach: Nichts! Im Allgemeinen werden diese Daten gar nicht
|
||||||
|
gespeichert. Im Zuge von Wartungsarbeiten o.Ä. kann es dazu kommen, dass diese Daten in Log-Files geschrieben werden,
|
||||||
|
damit wir Problemen auf den Grund gehen können. Diese werden dann allerdings mit keinen anderen Daten verknüpft und
|
||||||
|
direkt nach Beendigung der Arbeiten gelöscht!</p>
|
@ -0,0 +1,8 @@
|
|||||||
|
<p>Für deinen Usernamen wird automatisch ein E-Mail Konto mit dem Schema {username}@we.bstly.de (Beispiel für
|
||||||
|
den User foobar: foobar@we.bstly.de) erstellt. Außerdem wird ein sogenanntes @Catch-All für @{username}.we.bstly.de eingerichtet. Das
|
||||||
|
bedeutet, dass automatisch alle E-Mails an eine Adresse an @{username}.we.bstly.de in deinem Postfach landen. So hast
|
||||||
|
du eigentlich endlos viele E-Mail-Adressen zur Verfügung. Ein automatischer Spam-Filter ist natürlich auch dabei.
|
||||||
|
Ein Webmail Zugang steht ebenfalls zur Verfügung.</p>
|
||||||
|
|
||||||
|
<p>Für E-Mail Protokolle gibt es keinen OIDC-Login, dies bedeutet technisch, dass zum Login einfach die
|
||||||
|
Account-Datenbank von we.bstly genommen werden und somit dein we.bstly-Account Passwort.</p>
|
@ -0,0 +1,7 @@
|
|||||||
|
<p>Herzstück der digitalen Bastelei ist die Nextcloud. Nextcloud vereint einige Cloud-Dienste in einem. Ein einfaches
|
||||||
|
Dateisystem wie z.B. von Dropbox, einen Kalender wie z.B. von Google und und und. Die
|
||||||
|
Daten in der Nextcloud sind generell verschlüsselt, für volle Sicherheit deiner Dateien kannst du allerdings auch
|
||||||
|
die Ende-zu-Ende-Verschlüsselung der Nextcloud nutzen.</p>
|
||||||
|
|
||||||
|
<p>Über Nextcloud werden wir euch auch mit allen wichtigen Informationen über "Bastelei (bald e.V.)" informieren.
|
||||||
|
Des weiteren bietet Nextcloud auch einige Community-Funktionen, die wir gerne mit euch nutzen möchten.</p>
|
@ -0,0 +1,8 @@
|
|||||||
|
<p> Das we.bstly-System ist die zentrale Verwaltung für Account-Daten und Berechtigungen. Hier werden also Login-Daten
|
||||||
|
(Username + Password-Hash) und dein Public Key gespeichert, sowie verknüpfte Berechtigungen mit ihrer
|
||||||
|
Gültigkeitsdauer (Beispiel: Mitgliedschaft noch bis 24.03.2049, Git-Zugang bis 01.02.2027).</p>
|
||||||
|
|
||||||
|
<p>Implementiert ist dort ein <a href="https://openid.net/connect/" target="_blank">OpenID Connect (OIDC) Provider</a>
|
||||||
|
der den Zugriff auf die anderen Dienste ermöglicht.
|
||||||
|
Sprich, für alle weiteren Dienste (Ausnahme E-Mail, siehe unten) läuft der Login direkt über we.bstly (SSO - Single
|
||||||
|
Sign On).</p>
|
@ -0,0 +1,5 @@
|
|||||||
|
<p>Online Konferenzsoftware.</p>
|
||||||
|
|
||||||
|
<p>BigBlueButton ist ein mächtiges Konferenz-Tool, dass für den Einsatz an Schulen konzipiert wurde. Dadurch bietet es einen großen Umfang an Funktionen um viele denkbare Konferenz-Situationen abzubilden. Gerade aus Sicht der Bastelei und des Vereins bietet ein solches Tool Vorteile um größere Online-Konferenzen zu organisieren oder z.B. eine Mitgliederversammlung abzuhalten.</p>
|
||||||
|
|
||||||
|
<p class="hint">⚠️ Größerer Umfang an Funktionen bringt große Komplexität mit sich. Aktuell wird für BBB empfohlen einen eigenständigen Server zu verwenden. Hinzu kommen einige Sicherheitsbedenken, da die empfohlenen Systemvoraussetzungen veraltet sind. Außerdem bietet BBB derzeit auch keine OIDC-Unterstützung, was zusätzlich das Thema der Authentifizierung mit sich bringt.</p>
|
@ -0,0 +1,7 @@
|
|||||||
|
<p>Passwort Manager.</p>
|
||||||
|
|
||||||
|
<p>Ein Passwort Manager verwaltet sicher all deine Passwörter. Außerdem kannst du dir komplizierte Passwörter generieren lassen, so dass du für all deine Konten ein anderes, schwer zu erratendes Passwort hast und die Sicherheit deiner Konten erhöhst.</p>
|
||||||
|
|
||||||
|
<p>Bitwarden empfiehlt sich vor allem durch seine regelmäßigen unabhängigen Audits mit positiven Ergebnissen. Es gibt kaum vergleichbare Alternativen in selbst betriebenen Open-Source Password Managern.</p>
|
||||||
|
|
||||||
|
<p class="hint">⚠️ Aktuell bietet Bitwarden leider OIDC Unterstützung nur für eine bezahlte Premium Lizenz. Es gibt einen offenen <a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank">Rust-Klon</a> der Server Komponente, diese legt aber aktuell wenig Wert auf die Unterstützung des Organisationen-Feature und damit auf das damit verbundene OIDC. Somit bleibt abzuwarten, ob sich hier in nächster Zeit etwas entwickelt oder ob etwas für den Einsatz einer Alternative spricht.</p>
|
10
src/assets/templates/de-informal/services/planned/gitea.html
Normal file
10
src/assets/templates/de-informal/services/planned/gitea.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<p>Offene Source-Code Verwaltung auf Basis von git-Repositories (vergleichbar mit GitHub).</p>
|
||||||
|
|
||||||
|
<p>Gegenüber der Konkurrenz bietet Gitea vor allem den Vorteil, dass es selbst betrieben werden kann und man so die
|
||||||
|
Hoheit über seine Daten und seinen Code behält. Nennenswerte zusätzliche Features gegenüber vergleichbarer Software
|
||||||
|
gibt es nicht, der Funktionsumfang steht allerdings auch in nichts nach.</p>
|
||||||
|
|
||||||
|
<p class="hint">✅ Da Gitea auch OIDC Unterstützung mitbringt, stellt sich vor allem die Frage, ob für eine solche
|
||||||
|
Plattform Bedarf besteht. Da durch die Repositories natürlich einiges an Speicher hinzukommt, empfiehlt sich auch
|
||||||
|
hier eventuell ein separater Server, der dann von den Usern, die diesen Service nutzen wollen, finanziert werden
|
||||||
|
würde.</p>
|
@ -0,0 +1,13 @@
|
|||||||
|
<p>Online Konferenzsoftware.</p>
|
||||||
|
|
||||||
|
<p>Vor allem die Einfachheit in der Bedienung und aufs Nützlichste beschränkte Funktionen sind die Vorteile von Jitsi
|
||||||
|
Meet. Da es in jedem modernen Browser läuft, fällt auch die lästige Installation von Anwendungen auf Endgeräten weg.
|
||||||
|
Lediglich auf Smartphones wird die kostenlose Jitsi-App benötigt. Hier muss als Server dann der Bastelei-Server
|
||||||
|
angegeben werden.</p>
|
||||||
|
|
||||||
|
<p class="hint">❔ Bei Jitsi Meet ist eigentlich nur die Frage offen, ob es von den Serverkapazitäten möglich ist eine
|
||||||
|
offene Instanz zu betreiben bzw. ein eigener Server nötig ist, da die Software doch recht ressourcenhungrig ist.
|
||||||
|
Ansonsten muss sich _Bastler noch um eine Authentifizierungsmethode kümmern. OIDC wird nicht direkt unterstützt,
|
||||||
|
eine Beschränkung über VPN (Wireguard) wäre auch denkbar. Zu erwähnen ist hier, dass die Authentifizierung lediglich
|
||||||
|
für das Erstellen von Räumen nötig wäre. Jitsi Meet bietet eine Gästekonfiguration, so dass du so oder so beliebige
|
||||||
|
Personen zu deiner Konferenz einladen kannst.</p>
|
@ -0,0 +1,10 @@
|
|||||||
|
<p>Dezentrales Ende-zu-Ende-verschlüsseltes Messaging Protokoll.</p>
|
||||||
|
|
||||||
|
<p>Mit einem Matrix-Server können wir Teil eines dezentralen Messaging Netzwerk werden, welches mit einer E2EE die
|
||||||
|
größtmögliche Datensicherheit bietet. Es gibt verschiedene Clients, alles im allem ähnelt die Handhabung aber den
|
||||||
|
gängigen, bekannten Messenger wie Signal, WhatsApp, Threema oder Telegram.</p>
|
||||||
|
|
||||||
|
<p>Damit hätten wir auch ein verschlüsseltes Kommunikationssystem für unsere internen Nachrichten.</p>
|
||||||
|
|
||||||
|
<p class="hint">✅ Da der Synapse Server direkte Unterstützung für OIDC bietet, braucht es lediglich Serverkapazitäten
|
||||||
|
um diesen Service einzurichten.</p>
|
@ -0,0 +1,11 @@
|
|||||||
|
<p>Ad-Blocking via DNS (benötigt dann Wireguard ⚠️).</p>
|
||||||
|
|
||||||
|
<p>Werbeblocking über DNS bietet einige Vorteile gegenüber klassischem Ad-Blocking über Browser-Plugins. Da das Request
|
||||||
|
als solches blockiert wird, bekommt der Werbeserver nicht einmal mit, dass er gerade blockiert wird. Außerdem
|
||||||
|
funktioniert so ein Blocking dann für alle Geräte, Applikation usw. die auf das Internet zugreifen. So wird auch
|
||||||
|
Werbung und Tracking in mobilen Anwendungen blockiert.</p>
|
||||||
|
|
||||||
|
<p class="hint">❔ Pi-Hole selber einzurichten ist kein Problem. ⚠️ Allerdings würde eine offene Konfiguration für jeden
|
||||||
|
zugänglich sein, so dass Serverlasten nicht kontrollierbar wären. Deshalb ist die Idee von _Bastler, den Service
|
||||||
|
über ein VPN (Wireguard) zur Verfügung zu stellen. So ist Pi-Hole immer aktiv, sobald du eine gültige VPN Verbindung
|
||||||
|
zu unserem Server hast.</p>
|
@ -0,0 +1,14 @@
|
|||||||
|
<p>VPN Server.</p>
|
||||||
|
|
||||||
|
<p>Ein VPN Server bietet zum einen den Vorteil, dass deine echte IP-Adresse verschleiert wird, zum anderen würde ein
|
||||||
|
VPN uns ein paar technische Möglichkeiten bieten, da du dich als User dann in einem internen Netzwerk befindest,
|
||||||
|
worüber wir weitere Dienste oder Zugriffe auf bestimmte Dienste ermöglichen können.</p>
|
||||||
|
|
||||||
|
<p>Da natürlich sehr viel Traffic über den Server läuft wenn viele User im VPN sind, wird aktuell ein Hybrid-Betrieb
|
||||||
|
bevorzugt. Dies bedeutet dass du zwar weiterhin mit deiner eigenen IP direkt auf das Internet zugreifst, wir aber
|
||||||
|
eben durch das interne Netzwerk weitere Services zur Verfügung stellen können.</p>
|
||||||
|
|
||||||
|
<p class="hint">⚠️ In der Theorie ist das Aufsetzen des Servers kein Problem. Da die Authentifizierung über ein
|
||||||
|
Public-Private-Key Verfahren läuft, bräuchte es eigentlich nur ein kleines Script um den Public-Key eines Users
|
||||||
|
zur Konfiguration hinzuzufügen (oder zu entfernen). Das Szenario muss allerdings noch von _Bastler verifiziert und
|
||||||
|
getestet werden.</p>
|
@ -1 +0,0 @@
|
|||||||
<h1>Hello you lalala</h1>
|
|
@ -123,3 +123,57 @@ mat-form-field {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qrcode {
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
qrcode canvas {
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
max-width: 400px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-drawer-inner-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-sidenav-container {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
@media screen and (min-width: 576px) {
|
||||||
|
width: 540px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
width: 580px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 992px) {
|
||||||
|
width: 820px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1200px) {
|
||||||
|
width: 1000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user