add login with google and change

This commit is contained in:
blugee 2025-06-30 22:08:02 +05:30
parent 10d4cff4a4
commit d8ed8ce2ce
25 changed files with 1946 additions and 468 deletions

View file

@ -23,7 +23,7 @@ logging.basicConfig(level=logging.INFO)
log = logging.getLogger("updater")
log.setLevel(logging.INFO)
API_URL="http://host.docker.internal:8181"
API_URL="http://192.168.1.26:8181"
Config_File="/conf/server-conf.json"
Version_File="/usr/share/nginx/html/version.json"
# Example usage

View file

@ -18,7 +18,7 @@ server {
try_files $uri $uri/ /index.html =404;
}
location /api {
proxy_pass http://host.docker.internal:8181;
proxy_pass http://192.168.1.26:8181;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $realip_remote_addr;

546
package-lock.json generated
View file

@ -1,13 +1,13 @@
{
"name": "coreui-free-angular-admin-template",
"version": "4.5.27",
"name": "MikroWizard",
"version": "1.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "coreui-free-angular-admin-template",
"version": "4.5.27",
"license": "MIT",
"name": "MikroWizard",
"version": "1.0.1",
"license": "AGPL",
"dependencies": {
"@angular/animations": "^17.3.5",
"@angular/cdk": "^16.2.9",
@ -20,6 +20,8 @@
"@angular/platform-browser": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.5",
"@angular/router": "^17.3.5",
"@azure/msal-angular": "^4.0.12",
"@azure/msal-browser": "^4.12.0",
"@coreui/angular": "~4.5.27",
"@coreui/angular-chartjs": "~4.5.27",
"@coreui/chartjs": "^3.1.2",
@ -28,25 +30,26 @@
"@coreui/icons-angular": "~4.5.27",
"@coreui/utils": "^2.0.2",
"@easyfonts/font-awesome-v6": "^6.0.6",
"@fortawesome/angular-fontawesome": "^0.13.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/angular-fontawesome": "^0.15.0",
"@generic-ui/fabric": "^0.19.0",
"@generic-ui/hermes": "^0.19.0",
"@generic-ui/ngx-grid": "^0.19.0",
"apple-signin-auth": "^2.0.0",
"chart.js": "^3.9.1",
"date-fns": "^3.6.0",
"date-fns-jalali": "^3.6.0-0",
"date-fns-tz": "^3.1.3",
"diff-match-patch-ts": "^0.6.0",
"font-awesome": "^4.7.0",
"install": "^0.13.0",
"lodash-es": "^4.17.21",
"mat-progress-buttons": "^9.3.1",
"ngx-cron-editor": "^0.8.1",
"ngx-date-fns": "^11.0.0",
"ngx-diff": "^9.0.0",
"ngx-highlight-js": "^18.0.0",
"ngx-highlightjs": "^12.0.0",
"ngx-infinite-scroll": "^18.0.0",
"ngx-mat-select-search": "^7.0.6",
"ngx-material-date-fns-adapter": "^18.0.0",
"ngx-scrollbar": "^13.0.3",
@ -754,6 +757,37 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@azure/msal-angular": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/@azure/msal-angular/-/msal-angular-4.0.12.tgz",
"integrity": "sha512-J6wV84d0hcKjiH/VSCMDcigYiJ9bcrMuM3uFrfEATVn7ANziKq+skp9buwcr0s6e60Z3R7Juxb8wOnI1HemXGw==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@azure/msal-browser": "^4.12.0",
"rxjs": "^7.0.0"
}
},
"node_modules/@azure/msal-browser": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.12.0.tgz",
"integrity": "sha512-WD1lmVWchg7wn1mI7Tr4v7QPyTwK+8Nuyje3jRpOFENLRLEBsdK8VVdTw3C+TypZmYn4cOAdj3zREnuFXgvfIA==",
"dependencies": {
"@azure/msal-common": "15.6.0"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@azure/msal-common": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.6.0.tgz",
"integrity": "sha512-EotmBz42apYGjqiIV9rDUdptaMptpTn4TdGf3JfjLvFvinSe9BJ6ywU92K9ky+t/b0ghbeTSe9RfqlgLh8f2jA==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@ -2972,15 +3006,15 @@
}
},
"node_modules/@fortawesome/angular-fontawesome": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz",
"integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==",
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.15.0.tgz",
"integrity": "sha512-oxmJDYGNSym5ycFR0LX4ZOPAU+wWmMAznYpkm5DNAtWWkhMLcrZl15eZQmVIEE+qruQ7JiVrg3tpo8bEkFlDgw==",
"dependencies": {
"tslib": "^2.4.1"
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"tslib": "^2.6.2"
},
"peerDependencies": {
"@angular/core": "^16.0.0",
"@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0"
"@angular/core": "^18.0.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
@ -3004,42 +3038,6 @@
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz",
"integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz",
"integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
"integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@generic-ui/fabric": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@generic-ui/fabric/-/fabric-0.19.1.tgz",
@ -5390,6 +5388,19 @@
"node": ">= 8"
}
},
"node_modules/apple-signin-auth": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/apple-signin-auth/-/apple-signin-auth-2.0.0.tgz",
"integrity": "sha512-/O5gvAby7OU2K7baYQCzY0e0tCiHzhFkzt9L2v3bMd2I2w0ckCZ/4hdVUOrbolRGMppME+Zo8TAKPVru8aYnzg==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
"node-rsa": "^1.1.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -5405,6 +5416,15 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"dev": true
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/autoprefixer": {
"version": "10.4.18",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
@ -5770,6 +5790,12 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -6735,6 +6761,11 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true
},
"node_modules/diff-match-patch-ts": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/diff-match-patch-ts/-/diff-match-patch-ts-0.6.0.tgz",
"integrity": "sha512-U0uPIJ+wJqgaBoVw2MFSFpGIk7q3mJJ+/sehbxDZFv4Gx6a1GOmrsSLmxVDDrGtRL4Q9de084aa5lVpCHn+eUw=="
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -6832,6 +6863,15 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -8824,6 +8864,49 @@
"node >= 0.2.0"
]
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/karma": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz",
@ -9178,6 +9261,48 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -9728,8 +9853,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/multicast-dns": {
"version": "7.2.5",
@ -9757,6 +9881,7 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
@ -9846,6 +9971,28 @@
"date-fns": ">=3"
}
},
"node_modules/ngx-diff": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/ngx-diff/-/ngx-diff-9.1.0.tgz",
"integrity": "sha512-hD54tOsQLNU6ltbS9gV4wDrnZrMfxjF2AlxnUrp+/n2+T/nLK2bQMiiQT2IEe6CXZ1dl/doKD1qGoQnX0mSoIw==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": ">=18.0.0",
"@angular/core": ">=18.0.0",
"diff-match-patch-ts": ">=0.5.0"
}
},
"node_modules/ngx-highlight-js": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlight-js/-/ngx-highlight-js-18.0.0.tgz",
"integrity": "sha512-r/LSijb5Ju95ZGm89+4e905kFWkDt1XdGCg2prR1wleTE77uROscjZMht0D40WSRbxmOg+J2zw9OOZvsWSLeeg==",
"dependencies": {
"highlight.js": "^11.9.0",
"tslib": "^2.3.0"
}
},
"node_modules/ngx-highlightjs": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-12.0.0.tgz",
@ -9859,6 +10006,18 @@
"@angular/core": ">=17.0.0"
}
},
"node_modules/ngx-infinite-scroll": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-18.0.0.tgz",
"integrity": "sha512-D183TDwpsd9Zl56UmItsl3RzHdN25srAISfg6lc3A8mEKkEgOq0s7ZzRAYcx8DHsAkMgtZqjIPEvMifD3DOB/g==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=18.0.0 <19.0.0",
"@angular/core": ">=18.0.0 <19.0.0"
}
},
"node_modules/ngx-mat-select-search": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.6.tgz",
@ -10030,6 +10189,15 @@
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
},
"node_modules/node-rsa": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz",
"integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==",
"license": "MIT",
"dependencies": {
"asn1": "^0.2.4"
}
},
"node_modules/nopt": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
@ -10663,7 +10831,8 @@
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -10715,6 +10884,7 @@
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -11446,7 +11616,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
@ -11465,8 +11634,7 @@
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/safevalues": {
"version": "0.3.4",
@ -11579,7 +11747,6 @@
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -11594,7 +11761,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@ -11605,8 +11771,7 @@
"node_modules/semver/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/send": {
"version": "0.18.0",
@ -11997,6 +12162,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -14207,6 +14373,27 @@
"tslib": "^2.3.0"
}
},
"@azure/msal-angular": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/@azure/msal-angular/-/msal-angular-4.0.12.tgz",
"integrity": "sha512-J6wV84d0hcKjiH/VSCMDcigYiJ9bcrMuM3uFrfEATVn7ANziKq+skp9buwcr0s6e60Z3R7Juxb8wOnI1HemXGw==",
"requires": {
"tslib": "^2.3.0"
}
},
"@azure/msal-browser": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.12.0.tgz",
"integrity": "sha512-WD1lmVWchg7wn1mI7Tr4v7QPyTwK+8Nuyje3jRpOFENLRLEBsdK8VVdTw3C+TypZmYn4cOAdj3zREnuFXgvfIA==",
"requires": {
"@azure/msal-common": "15.6.0"
}
},
"@azure/msal-common": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.6.0.tgz",
"integrity": "sha512-EotmBz42apYGjqiIV9rDUdptaMptpTn4TdGf3JfjLvFvinSe9BJ6ywU92K9ky+t/b0ghbeTSe9RfqlgLh8f2jA=="
},
"@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@ -14580,8 +14767,7 @@
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
"dev": true,
"requires": {}
"dev": true
},
"@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
@ -15659,11 +15845,12 @@
"optional": true
},
"@fortawesome/angular-fontawesome": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz",
"integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==",
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.15.0.tgz",
"integrity": "sha512-oxmJDYGNSym5ycFR0LX4ZOPAU+wWmMAznYpkm5DNAtWWkhMLcrZl15eZQmVIEE+qruQ7JiVrg3tpo8bEkFlDgw==",
"requires": {
"tslib": "^2.4.1"
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"tslib": "^2.6.2"
}
},
"@fortawesome/fontawesome-common-types": {
@ -15679,30 +15866,6 @@
"@fortawesome/fontawesome-common-types": "6.5.2"
}
},
"@fortawesome/free-brands-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz",
"integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.5.2"
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz",
"integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.5.2"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
"integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.5.2"
}
},
"@generic-ui/fabric": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@generic-ui/fabric/-/fabric-0.19.1.tgz",
@ -16676,8 +16839,7 @@
"version": "17.3.5",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.5.tgz",
"integrity": "sha512-0heI0yHUckdGI8uywu/wkp24KR/tdYMKYJOaYIU+9JydyN1zJRpbR7x0thddl7+k/zu2ZGbfFdv1779Ecw/xdA==",
"dev": true,
"requires": {}
"dev": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@ -17380,8 +17542,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz",
"integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==",
"dev": true,
"requires": {}
"dev": true
},
"@webassemblyjs/ast": {
"version": "1.12.1",
@ -17573,8 +17734,7 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"requires": {}
"dev": true
},
"adjust-sourcemap-loader": {
"version": "4.0.0",
@ -17694,6 +17854,15 @@
"picomatch": "^2.0.4"
}
},
"apple-signin-auth": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/apple-signin-auth/-/apple-signin-auth-2.0.0.tgz",
"integrity": "sha512-/O5gvAby7OU2K7baYQCzY0e0tCiHzhFkzt9L2v3bMd2I2w0ckCZ/4hdVUOrbolRGMppME+Zo8TAKPVru8aYnzg==",
"requires": {
"jsonwebtoken": "^9.0.0",
"node-rsa": "^1.1.1"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -17709,6 +17878,14 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"dev": true
},
"asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"requires": {
"safer-buffer": "~2.1.0"
}
},
"autoprefixer": {
"version": "10.4.18",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
@ -17961,6 +18138,11 @@
"ieee754": "^1.1.13"
}
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -18596,8 +18778,7 @@
"date-fns-tz": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
"integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
"requires": {}
"integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA=="
},
"date-format": {
"version": "4.0.14",
@ -18673,6 +18854,11 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true
},
"diff-match-patch-ts": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/diff-match-patch-ts/-/diff-match-patch-ts-0.6.0.tgz",
"integrity": "sha512-U0uPIJ+wJqgaBoVw2MFSFpGIk7q3mJJ+/sehbxDZFv4Gx6a1GOmrsSLmxVDDrGtRL4Q9de084aa5lVpCHn+eUw=="
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -18746,6 +18932,14 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -18826,8 +19020,7 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"requires": {}
"dev": true
}
}
},
@ -19677,8 +19870,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true,
"requires": {}
"dev": true
},
"ieee754": {
"version": "1.2.1",
@ -20226,6 +20418,42 @@
"integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
"dev": true
},
"jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
}
},
"jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"requires": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"karma": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz",
@ -20374,8 +20602,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz",
"integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==",
"dev": true,
"requires": {}
"dev": true
},
"karma-source-map-support": {
"version": "1.4.0",
@ -20497,6 +20724,41 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -20917,8 +21179,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "7.2.5",
@ -20939,7 +21200,8 @@
"nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true
},
"needle": {
"version": "3.3.1",
@ -20992,6 +21254,23 @@
"tslib": "^2.3.0"
}
},
"ngx-diff": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/ngx-diff/-/ngx-diff-9.1.0.tgz",
"integrity": "sha512-hD54tOsQLNU6ltbS9gV4wDrnZrMfxjF2AlxnUrp+/n2+T/nLK2bQMiiQT2IEe6CXZ1dl/doKD1qGoQnX0mSoIw==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-highlight-js": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlight-js/-/ngx-highlight-js-18.0.0.tgz",
"integrity": "sha512-r/LSijb5Ju95ZGm89+4e905kFWkDt1XdGCg2prR1wleTE77uROscjZMht0D40WSRbxmOg+J2zw9OOZvsWSLeeg==",
"requires": {
"highlight.js": "^11.9.0",
"tslib": "^2.3.0"
}
},
"ngx-highlightjs": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-12.0.0.tgz",
@ -21001,6 +21280,14 @@
"tslib": "^2.3.0"
}
},
"ngx-infinite-scroll": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-18.0.0.tgz",
"integrity": "sha512-D183TDwpsd9Zl56UmItsl3RzHdN25srAISfg6lc3A8mEKkEgOq0s7ZzRAYcx8DHsAkMgtZqjIPEvMifD3DOB/g==",
"requires": {
"tslib": "^2.3.0"
}
},
"ngx-mat-select-search": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.6.tgz",
@ -21121,6 +21408,14 @@
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
},
"node-rsa": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz",
"integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==",
"requires": {
"asn1": "^0.2.4"
}
},
"nopt": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
@ -21585,7 +21880,8 @@
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": {
"version": "2.3.1",
@ -21622,6 +21918,7 @@
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"requires": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
@ -21657,8 +21954,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
"integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
"dev": true,
"requires": {}
"dev": true
},
"postcss-modules-local-by-default": {
"version": "4.0.5",
@ -22137,14 +22433,12 @@
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"safevalues": {
"version": "0.3.4",
@ -22210,7 +22504,6 @@
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
},
@ -22219,7 +22512,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
@ -22227,8 +22519,7 @@
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
@ -22490,8 +22781,7 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"requires": {}
"dev": true
}
}
},
@ -22546,7 +22836,8 @@
"source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg=="
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true
},
"source-map-loader": {
"version": "5.0.0",
@ -22849,8 +23140,7 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
@ -23399,8 +23689,7 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
@ -23637,8 +23926,7 @@
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"dev": true,
"requires": {}
"dev": true
},
"y18n": {
"version": "5.0.8",

View file

@ -11,7 +11,7 @@
},
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0",
"start": "ng serve --host 0.0.0.0 --port 4200 --disable-host-check",
"startnp": "ng serve --proxy-config=./proxy.conf.ts --host 0.0.0.0",
"build": "ng build",
"watch": "ng build --watch --configuration development",
@ -30,6 +30,8 @@
"@angular/platform-browser": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.5",
"@angular/router": "^17.3.5",
"@azure/msal-angular": "^4.0.12",
"@azure/msal-browser": "^4.12.0",
"@coreui/angular": "~4.5.27",
"@coreui/angular-chartjs": "~4.5.27",
"@coreui/chartjs": "^3.1.2",
@ -42,6 +44,7 @@
"@generic-ui/fabric": "^0.19.0",
"@generic-ui/hermes": "^0.19.0",
"@generic-ui/ngx-grid": "^0.19.0",
"apple-signin-auth": "^2.0.0",
"chart.js": "^3.9.1",
"date-fns": "^3.6.0",
"date-fns-jalali": "^3.6.0-0",

View file

@ -1,8 +1,9 @@
{
"/api": {
"target": "http://172.17.0.1:8181/",
"target": "http://192.168.1.26:3000/",
"secure": false,
"changeOrigin": true,
"logLevel": "debug",
"pathRewrite": {"^/api" : ""}
}
}

View file

@ -5,6 +5,7 @@ import { DefaultLayoutComponent } from './containers';
import { Page404Component } from './views/pages/page404/page404.component';
import { Page500Component } from './views/pages/page500/page500.component';
import { LoginComponent } from './views/pages/login/login.component';
import { SignupComponent } from './views/pages/signup/signup.component';
const routes: Routes = [
{
@ -132,6 +133,13 @@ const routes: Routes = [
title: 'Login Page'
}
},
{
path: 'signup',
component: SignupComponent,
data: {
title: 'Signup Page'
}
},
{path: '**', redirectTo: 'dashboard'}
];

View file

@ -1,8 +1,12 @@
import { NgModule ,APP_INITIALIZER} from '@angular/core';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HashLocationStrategy, LocationStrategy, PathLocationStrategy } from '@angular/common';
import { BrowserModule, Title } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule,FormsModule } from '@angular/forms';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { MsalModule, MsalService, MSAL_INSTANCE, MsalGuard, MsalInterceptor, MsalGuardConfiguration, MsalInterceptorConfiguration } from '@azure/msal-angular';
import { IPublicClientApplication, PublicClientApplication, InteractionType } from '@azure/msal-browser';
import { msalConfig } from './auth/msal-config';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { HttpClientModule } from '@angular/common/http';
@ -54,12 +58,36 @@ const APP_CONTAINERS = [
export function loginStatusProviderFactory(provider: loginChecker) {
return () => provider.load();
}
export function MSALInstanceFactory(): IPublicClientApplication {
const msalInstance = new PublicClientApplication(msalConfig);
msalInstance.initialize().catch(error => {
console.error('MSAL initialization failed:', error);
});
return msalInstance;
}
const guardConfig: MsalGuardConfiguration = {
interactionType: InteractionType.Popup,
authRequest: {
scopes: ['User.Read', 'profile', 'email', 'openid']
}
};
const interceptorConfig: MsalInterceptorConfiguration = {
interactionType: InteractionType.Popup,
protectedResourceMap: new Map([
['https://graph.microsoft.com/v1.0/me', ['User.Read']]
])
};
@NgModule({
declarations: [AppComponent, ...APP_CONTAINERS],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
MsalModule.forRoot(MSALInstanceFactory(), guardConfig, interceptorConfig),
AvatarModule,
BreadcrumbModule,
FooterModule,
@ -94,6 +122,17 @@ export function loginStatusProviderFactory(provider: loginChecker) {
provide: LocationStrategy,
useClass: HashLocationStrategy
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
MsalService,
MsalGuard,
MikroWizardProvider,
dataProvider,
loginChecker,

View file

@ -0,0 +1,7 @@
export const appleConfig = {
clientId: 'ca.circleprotect.cnode', // Your Apple Service ID
redirectURI: "https://30c8-2402-a00-162-359c-1499-994a-62e-7eda.ngrok-free.app/auth/apple/callback",
scope: 'name email',
state: 'apple-signin',
usePopup: true
};

View file

@ -0,0 +1,19 @@
import { Configuration, PopupRequest } from "@azure/msal-browser";
// MSAL configuration
export const msalConfig: Configuration = {
auth: {
clientId: "fdbc76a1-2553-4efb-871f-56afdd3dd894", // Replace with your Azure AD application client ID
authority: "https://login.microsoftonline.com/common",
redirectUri: window.location.origin,
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
}
};
// Add here scopes for id token to be used at MS Identity Platform endpoints.
export const loginRequest: PopupRequest = {
scopes: ["User.Read", "profile", "email", "openid"]
};

View file

@ -89,7 +89,8 @@ import {
cilUserFemale,
cilUserFollow,
cilUserUnfollow,
cilExitToApp
cilExitToApp,
cilBuilding
} from '@coreui/icons';
export const iconSubset = {
@ -183,7 +184,8 @@ export const iconSubset = {
cilUserFemale,
cilUserFollow,
cilUserUnfollow,
cilExitToApp
cilExitToApp,
cilBuilding
};
export enum IconSubset {
@ -277,5 +279,6 @@ export enum IconSubset {
cilUserFemale = 'cilUserFemale',
cilUserFollow = 'cilUserFollow',
cilUserUnfollow = 'cilUserUnfollow',
cilExitToApp = 'cilExitToApp'
cilExitToApp = 'cilExitToApp',
cilBuilding = 'cilBuilding'
}

View file

@ -1,28 +1,47 @@
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { dataProvider } from './mikrowizard/data';
@Injectable()
export class loginChecker {
private logged_in: boolean = false;
private pinging: boolean = false;
private is_logged_in: boolean = false;
private public_routes = ['/login', '/signup'];
constructor(private data_provider: dataProvider) {
constructor(
private router: Router,
private data_provider: dataProvider
) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
const currentRoute = event.url;
if (!this.is_logged_in && !this.public_routes.includes(currentRoute)) {
this.router.navigate(['/login']);
}
}
});
}
public isLoggedIn(): boolean {
return this.logged_in;
isLoggedIn() {
return this.is_logged_in;
}
setStatus(status: boolean) {
this.is_logged_in = status;
}
load() {
var _self = this;
return this.data_provider.isLoggedIn().then(result => {
_self.logged_in = result;
}).catch(err => {
_self.logged_in = false;
})
}
setStatus(status: boolean): void {
this.logged_in = status;
}
setPinging(ping: boolean): void {
this.pinging = ping;
return new Promise((resolve, reject) => {
this.data_provider.getSessionInfo().then(res => {
if ('uid' in res && res['uid']) {
this.is_logged_in = true;
} else {
this.is_logged_in = false;
}
resolve(true);
}).catch(err => {
this.is_logged_in = false;
resolve(true);
});
});
}
}

View file

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// import { MikroWizardrpcProvider } from '../MikroWizardrpc/MikroWizardrpc';
import { MikroWizardProvider } from './provider';
@ -10,13 +10,15 @@ import { User } from './user';
export class dataProvider {
// public serverUrl: string = "/api";
public serverUrl: string = "";
public serverUrl: string = "http://192.168.1.26:3000";
private db: string = "NothingImportant";
private apiUrl: string = "/api";
constructor(
// private http: HTTP,
// public MikroWizardRPC: MikroWizardrpcProvider,
public MikroWizardRPC: MikroWizardProvider,
private http: HttpClient
) {
this.MikroWizardRPC.init({
MikroWizard_server: this.serverUrl
@ -628,4 +630,36 @@ export class dataProvider {
getFullUrl(url: any) {
return this.serverUrl + url;
}
signup(username: string, organization: string, email: string, password: string) {
var data = {
'username': username,
'organization': organization,
'email': email,
'password': password
}
return this.MikroWizardRPC.sendJsonRequest("/api/signup", data);
}
async loginWithOffice365(tokenClaims: any): Promise<any> {
try {
const data = {
tokenClaims: tokenClaims
};
return this.MikroWizardRPC.sendJsonRequest("/api/auth/office365", data);
} catch (error) {
throw error;
}
}
async loginWithApple(appleResponse: any): Promise<any> {
try {
const data = {
appleResponse: appleResponse
};
return this.MikroWizardRPC.sendJsonRequest("/api/auth/apple", data);
} catch (error) {
throw error;
}
}
}

View file

@ -124,7 +124,7 @@ export class MikroWizardProvider {
let headers = {
"Content-Type": "application/json",
};
return this.http.post(url, params,
return this.http.post(this.MikroWizard_server + url, params,
{headers:this.headers,withCredentials:true}
).toPromise()
.then(this.handleRequestErrors)
@ -145,7 +145,7 @@ export class MikroWizardProvider {
let headers = {
"Content-Type": "application/x-www-form-urlencoded",
};
return this.http.get( url,
return this.http.get( this.MikroWizard_server + url,
{ responseType: 'json' }
).toPromise()
}

View file

@ -1,224 +1,260 @@
<c-row *ngIf="stats">
<c-col xs>
<c-card *ngIf="stats" class="mb-1">
<c-card-header>Past 24 Hour Statics</c-card-header>
<c-card-body>
<c-row>
<c-col md="12" xl="12" xs="12">
<c-row>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Failed Logins'" class="mb-1" color="danger" padding
value="{{stats['FailedLogins']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-person-circle-exclamation"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Success Logins'" class="mb-1" color="success" padding
value="{{stats['SuccessfulLogins']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-arrow-right-to-bracket"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Critical Events'" class="mb-1" color="danger" padding
value="{{stats['Critical']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-skull-crossbones"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Warning Events'" class="mb-1" color="warning" padding
value="{{stats['Warning']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-triangle-exclamation"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Info Events'" class="mb-1" color="info" padding value="{{stats['Info']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-circle-info"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
</c-row>
</c-col>
</c-row>
</c-card-body>
<c-card-footer class="pb-0">
<c-col xs>
<c-row>
<!-- Dashboard Header with Customization Controls -->
<div class="dashboard-header mb-3">
<div class="d-flex justify-content-between align-items-center">
<h2>Dashboard</h2>
<div class="dashboard-controls">
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="dashboardActions" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-plus"></i> Customize Dashboard
</button>
<ul class="dropdown-menu" aria-labelledby="dashboardActions">
<li><a class="dropdown-item" href="#" (click)="showWizardModal()"><i class="fa-solid fa-magic"></i> ADD WIZARD</a></li>
<li><a class="dropdown-item" href="#" (click)="showViewModal()"><i class="fa-solid fa-eye"></i> ADD VIEW</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" (click)="toggleRemoveMode()"><i class="fa-solid fa-trash"></i> REMOVE</a></li>
</ul>
</div>
<button *ngIf="isRemoveMode" class="btn btn-secondary ms-2" (click)="toggleRemoveMode()">
<i class="fa-solid fa-times"></i> Cancel Remove
</button>
</div>
</div>
</div>
<!-- Dashboard Grid Container -->
<div class="dashboard-grid" [class.remove-mode]="isRemoveMode">
<!-- Default Stats Widget -->
<div class="dashboard-widget" *ngIf="widgets['stats'].enabled" [style.grid-column]="widgets['stats'].position.col" [style.grid-row]="widgets['stats'].position.row">
<div class="widget-container">
<button *ngIf="isRemoveMode" class="btn btn-danger btn-sm widget-remove-btn" (click)="removeWidget('stats')">
<i class="fa-solid fa-times"></i>
</button>
<c-card class="h-100">
<c-card-header class="d-flex justify-content-between align-items-center">
<span>Past 24 Hour Statistics</span>
<i class="fa-solid fa-grip-vertical widget-drag-handle"></i>
</c-card-header>
<c-card-body>
<c-row *ngIf="stats">
<c-col md="12" xl="12" xs="12">
<c-row>
<c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-info pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total users</div>
<div class="fs-6 fw-semibold">{{stats['Users']}}</div>
</div>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Failed Logins'" class="mb-1" color="danger" padding
value="{{stats['FailedLogins']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-person-circle-exclamation"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-warning pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total Devices</div>
<div class="fs-6 fw-semibold">{{stats['Devices']}}</div>
</div>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Success Logins'" class="mb-1" color="success" padding
value="{{stats['SuccessfulLogins']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-arrow-right-to-bracket"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-success pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total Events</div>
<div class="fs-6 fw-semibold">{{stats['Events']}}</div>
</div>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Critical Events'" class="mb-1" color="danger" padding
value="{{stats['Critical']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-skull-crossbones"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-success pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total Auth Logs</div>
<div class="fs-6 fw-semibold">{{stats['Auth']}}</div>
</div>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Warning Events'" class="mb-1" color="warning" padding
value="{{stats['Warning']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-triangle-exclamation"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
<c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-success pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total Acc Logs</div>
<div class="fs-6 fw-semibold">{{stats['Acc']}}</div>
</div>
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Info Events'" class="mb-1" color="info" padding value="{{stats['Info']}}">
<ng-template cTemplateId="widgetIconTemplate">
<i style="font-size: 2em;" class="fa-solid fa-circle-info"></i>
</ng-template>
</c-widget-stat-f>
</c-col>
</c-row>
</c-col>
</c-row>
</c-col>
</c-card-footer>
</c-card>
</c-col>
</c-row>
</c-card-body>
</c-card>
</div>
</div>
<c-card class="mb-1">
<c-card-body>
<c-row>
<c-col sm="5">
<h4 class="card-title mb-0" id="traffic">Total Devices Traffic</h4>
</c-col>
<c-col class="d-none d-md-block" sm="7">
<form [formGroup]="trafficRadioGroup">
<c-button-group class="float-end me-3" role="group">
<input class="btn-check" formControlName="trafficRadio" type="radio" value="5m" />
<label (click)="setTrafficPeriod('5m')" cButton cFormCheckLabel color="secondary" variant="outline">5
Minues</label>
<!-- Traffic Chart Widget -->
<div class="dashboard-widget" *ngIf="widgets['traffic'].enabled" [style.grid-column]="widgets['traffic'].position.col" [style.grid-row]="widgets['traffic'].position.row">
<div class="widget-container">
<button *ngIf="isRemoveMode" class="btn btn-danger btn-sm widget-remove-btn" (click)="removeWidget('traffic')">
<i class="fa-solid fa-times"></i>
</button>
<c-card class="h-100">
<c-card-header class="d-flex justify-content-between align-items-center">
<h4 class="card-title mb-0">Total Devices Traffic</h4>
<i class="fa-solid fa-grip-vertical widget-drag-handle"></i>
</c-card-header>
<c-card-body>
<form [formGroup]="trafficRadioGroup" class="mb-3">
<c-button-group class="float-end me-3" role="group">
<input class="btn-check" formControlName="trafficRadio" type="radio" value="5m" />
<label (click)="setTrafficPeriod('5m')" cButton cFormCheckLabel color="secondary" variant="outline">5 Minutes</label>
<input class="btn-check" formControlName="trafficRadio" type="radio" value="1h" />
<label (click)="setTrafficPeriod('1h')" cButton cFormCheckLabel color="secondary" variant="outline">Hourly</label>
<input class="btn-check" formControlName="trafficRadio" type="radio" value="daily" />
<label (click)="setTrafficPeriod('daily')" cButton cFormCheckLabel color="secondary" variant="outline">Daily</label>
<input class="btn-check" formControlName="trafficRadio" type="radio" value="live" />
<label (click)="setTrafficPeriod('live')" cButton cFormCheckLabel color="secondary" variant="outline">Live</label>
</c-button-group>
</form>
<c-chart [data]="chart_data" [options]="options" [height]="250" type="line"></c-chart>
</c-card-body>
</c-card>
</div>
</div>
<input class="btn-check" formControlName="trafficRadio" type="radio" value="1h" />
<label (click)="setTrafficPeriod('1h')" cButton cFormCheckLabel color="secondary"
variant="outline">Hourly</label>
<input class="btn-check" formControlName="trafficRadio" type="radio" value="daily" />
<label (click)="setTrafficPeriod('daily')" cButton cFormCheckLabel color="secondary"
variant="outline">Daily</label>
<input class="btn-check" formControlName="trafficRadio" type="radio" value="live" />
<label (click)="setTrafficPeriod('live')" cButton cFormCheckLabel color="secondary"
variant="outline">Live</label>
</c-button-group>
</form>
</c-col>
</c-row>
<c-chart [data]="chart_data" [options]="options" [height]="250" type="line">
</c-chart>
</c-card-body>
</c-card>
<c-row>
<c-col xl="6" *ngIf="stats" lg="12" class="h-100" style="min-height: 160px!important;display: grid">
<c-card class="mb-1 p-1 h-100" style="padding-left: 5px!important;">
<div class="my-1">
<h4 style="display: inline-block;">Version and Serial information</h4>
</div>
<div *ngIf="!stats['license']" class="my-1">
<div style="display: inline-block;margin-right: 5px;">
<code style="padding: 0!important;">Serial:</code> <small
style="background-color: #ccc;padding: 5px;border-radius: 5px;cursor: pointer;" (click)="copy_this()"
[cdkCopyToClipboard]="stats['serial']">{{ stats['serial'] }}</small>
<span *ngIf="copy_msg" style="color: #fff!important;" class="badge text-bg-success"><i
class="fa-solid fa-check"></i>Copy</span>
</div>
<c-badge *ngIf="stats['username']" color="danger">Not Registred</c-badge>
<c-badge *ngIf="!stats['username']" color="danger">License Validation failed</c-badge>
</div>
<div *ngIf="stats['license']=='connection_error'" class="my-1">
<div style="display: inline-block;margin-right: 5px;">
<code style="padding: 0!important;">Serial:</code> <small
style="background-color: #ccc;padding: 5px;border-radius: 5px;cursor: pointer;" (click)="copy_this()"
[cdkCopyToClipboard]="stats['serial']">{{ stats['serial'] }}</small>
<span *ngIf="copy_msg" style="color: #fff!important;" class="badge text-bg-success mx-1"><i
class="fa-solid fa-check"></i>Copy</span>
</div>
<c-badge class="mx-1" color="danger">Unable connect to server/Check server internet connection</c-badge>
</div>
<div *ngIf="stats['license']!='connection_error' && stats['license']" class="my-1">
<div style="display: inline-block;margin-right: 5px;">
<code style="padding: 0!important;">Serial:</code> <small
style="background-color: #ccc;padding: 5px;border-radius: 5px;cursor: pointer;" (click)="copy_this()"
[cdkCopyToClipboard]="stats['serial']">{{ stats['serial'] }}</small>
<span *ngIf="copy_msg" style="color: #fff!important;" class="badge text-bg-success mx-1"><i
class="fa-solid fa-check"></i>Copy</span>
</div>
<c-badge color="success">Registred</c-badge>
<c-badge class="mx-1" color="info">License Type : {{stats['license']}}</c-badge>
<c-badge *ngIf="stats['update_mode']!='auto'" color="info">Manual update</c-badge>
<c-badge *ngIf="stats['update_mode']=='auto'" color="info">Auto update</c-badge>
</div>
<div *ngIf="stats['license']!='connection_error'" class="my-1">
<span style="font-size: 0.9rem; display: inline-block;margin-right: 5px"><c-badge
[color]="stats['update_available'] ? 'success' : 'secondary'"
style="margin: 0!important;padding: 8px;height: 27px;">Your Mikroman version : {{stats['version']}}
</c-badge>
<i class="fa-solid fa-spinner fa-spin" *ngIf="stats['update_inprogress']"></i>
<button cButton color="warning"
*ngIf="stats['update_mode']!='auto' && stats['update_available'] && !stats['update_inprogress']" size="sm"
(click)="showConfirmModal('update_mikroman')"
style="font-size: 0.75em;position: relative;left: -4px;top: 1px;border-top-left-radius: 0;border-bottom-left-radius: 0;height: 27px;"><i
class="fa-regular fa-hand-pointer fa-beat-fade"></i> Update availble </button>
</span>
<span style="font-size: 0.9rem; display: inline-block;"><c-badge
[color]="stats['front_update_available'] ? 'success' : 'secondary'" style="padding: 8px;height: 27px;"
color="secondary">Your Mikrofront version : {{front_version}}
</c-badge>
<i class="fa-solid fa-spinner fa-spin" *ngIf="stats['front_update_inprogress']"></i>
<button cButton color="warning"
*ngIf="stats['update_mode']!='auto' && stats['front_update_available'] && !stats['front_update_inprogress']"
size="sm" (click)="showConfirmModal('update_mikrofront')"
style="font-size: 0.75em;position: relative;left: -4px;top: 1px;border-top-left-radius: 0;border-bottom-left-radius: 0;height: 27px;"><i
class="fa-regular fa-hand-pointer fa-beat-fade"></i> Update availble </button>
</span>
</div>
<p *ngIf="!stats['license'] && !stats['username']" style="color: rgb(0, 119, 255);"><strong>License User name is not set in settings <a style="color: rgb(0, 119, 255);" target="_blank" href="https://mikrowizard.com/docs/register-serial-number/" >read more!</a></strong></p>
<p *ngIf="!stats['license'] && stats['username']" style="color: rgb(0, 119, 255);"><strong>Serial number not submited<a style="color: rgb(0, 119, 255);" target="_blank" href="https://mikrowizard.com/docs/register-serial-number/" >read more!</a></strong> </p>
<!-- <div *ngIf="stats['update_mode']!='auto'" class="my-1">
<button cButton color="warning" *ngIf="stats['update_available']" size="sm" style="font-size: 1em;"><i class="fa-regular fa-hand-pointer fa-beat-fade"></i> Update <strong>Mikroman</strong> and reload server</button>
<button cButton color="warning" *ngIf="stats['front_update_available']" size="sm" style="font-size: 1em;margin-left: 5px;"><i class="fa-regular fa-hand-pointer fa-beat-fade"></i> Update <strong>MikroFront</strong> and reload Page</button>
</div> -->
</c-card>
</c-col>
<c-col xl="6" lg="12" class="h-100" style="min-height: 160px!important;display: grid;">
<c-card class="h-100" *ngIf="stats" style="padding: 0!important;margin: 0!important;">
<c-carousel [dark]="true" [animate]="false" [wrap]="false" [interval]="1000000">
<c-carousel-indicators></c-carousel-indicators>
<c-carousel-inner>
<c-carousel-item style="display: flex;" *ngFor="let slide of stats['blog']; index as i;">
<img [src]="slide.media_content" alt="{{slide.title}}" class="d-block" loading="lazy" style=" float: left;"
height="150px" />
<div style="padding: 20px;">
<h5>{{slide.title}}</h5>
<p style="max-width: 90%;" [innerHTML]="slide.summery"></p>
<!-- Custom Wizard Widgets -->
<div class="dashboard-widget" *ngFor="let widget of customWidgets; let i = index"
[style.grid-column]="widget.position.col" [style.grid-row]="widget.position.row">
<div class="widget-container">
<button *ngIf="isRemoveMode" class="btn btn-danger btn-sm widget-remove-btn" (click)="removeCustomWidget(i)">
<i class="fa-solid fa-times"></i>
</button>
<c-card class="h-100">
<c-card-header class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">{{widget.title}}</h5>
<i class="fa-solid fa-grip-vertical widget-drag-handle"></i>
</c-card-header>
<c-card-body>
<!-- CPU Widget -->
<div *ngIf="widget.type === 'cpu'">
<div class="text-center">
<canvas id="cpu-chart-{{i}}" width="200" height="200"></canvas>
<h3 class="mt-2">{{widget.data?.usage || 0}}%</h3>
<p class="text-muted">CPU Usage</p>
</div>
</c-carousel-item>
</c-carousel-inner>
<c-carousel-control [routerLink] caption="Previous" direction="prev"></c-carousel-control>
<c-carousel-control [routerLink] caption="Next" direction="next"></c-carousel-control>
</c-carousel>
</c-card>
</c-col>
</c-row>
</div>
<!-- Memory Widget -->
<div *ngIf="widget.type === 'memory'">
<div class="progress mb-2" style="height: 20px;">
<div class="progress-bar" role="progressbar" [style.width.%]="widget.data?.usage || 0">
{{widget.data?.usage || 0}}%
</div>
</div>
<p class="text-muted">Memory Usage: {{widget.data?.used || 0}}GB / {{widget.data?.total || 0}}GB</p>
</div>
<!-- Disk Widget -->
<div *ngIf="widget.type === 'disk'">
<div class="progress mb-2" style="height: 20px;">
<div class="progress-bar bg-warning" role="progressbar" [style.width.%]="widget.data?.usage || 0">
{{widget.data?.usage || 0}}%
</div>
</div>
<p class="text-muted">Disk Usage: {{widget.data?.used || 0}}GB / {{widget.data?.total || 0}}GB</p>
</div>
<!-- Network Widget -->
<div *ngIf="widget.type === 'network'">
<div class="row">
<div class="col-6 text-center">
<h4 class="text-success">{{widget.data?.download || 0}}</h4>
<small class="text-muted">Download</small>
</div>
<div class="col-6 text-center">
<h4 class="text-primary">{{widget.data?.upload || 0}}</h4>
<small class="text-muted">Upload</small>
</div>
</div>
</div>
</c-card-body>
</c-card>
</div>
</div>
<!-- Custom View Widgets -->
<div class="dashboard-widget" *ngFor="let view of customViews; let i = index"
[style.grid-column]="view.position.col" [style.grid-row]="view.position.row">
<div class="widget-container">
<button *ngIf="isRemoveMode" class="btn btn-danger btn-sm widget-remove-btn" (click)="removeCustomView(i)">
<i class="fa-solid fa-times"></i>
</button>
<c-card class="h-100">
<c-card-header class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">{{view.name}}</h5>
<i class="fa-solid fa-grip-vertical widget-drag-handle"></i>
</c-card-header>
<c-card-body>
<p class="text-muted">Custom view content will be loaded here...</p>
<div class="text-center">
<i class="fa-solid fa-eye fa-3x text-secondary"></i>
</div>
</c-card-body>
</c-card>
</div>
</div>
</div>
<!-- Add Wizard Modal -->
<c-modal #AddWizardModal backdrop="static" size="lg" [(visible)]="wizardModalVisible" id="AddWizardModal">
<c-modal-header>
<h5 cModalTitle>ADD WIZARD</h5>
<button [cModalToggle]="AddWizardModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<form [formGroup]="wizardForm">
<div class="mb-3">
<label for="wizardType" class="form-label">Select Wizard Type</label>
<select class="form-select" formControlName="wizardType" id="wizardType">
<option value="">Choose wizard type...</option>
<option value="cpu">CPU Monitor</option>
<option value="memory">Memory Monitor</option>
<option value="disk">Disk Monitor</option>
<option value="network">Network Monitor</option>
</select>
</div>
<div class="mb-3">
<label for="wizardTitle" class="form-label">Widget Title</label>
<input type="text" class="form-control" formControlName="wizardTitle" id="wizardTitle" placeholder="Enter widget title">
</div>
</form>
</c-modal-body>
<c-modal-footer>
<button cButton (click)="commitWizard()" color="primary" [disabled]="!wizardForm.valid">Commit</button>
<button [cModalToggle]="AddWizardModal.id" cButton color="secondary">Cancel</button>
</c-modal-footer>
</c-modal>
<!-- Add View Modal -->
<c-modal #AddViewModal backdrop="static" size="lg" [(visible)]="viewModalVisible" id="AddViewModal">
<c-modal-header>
<h5 cModalTitle>ADD VIEW</h5>
<button [cModalToggle]="AddViewModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<form [formGroup]="viewForm">
<div class="mb-3">
<label for="viewName" class="form-label">View Name</label>
<input type="text" class="form-control" formControlName="viewName" id="viewName" placeholder="Enter view name">
</div>
<div class="mb-3">
<label for="viewDescription" class="form-label">Description (Optional)</label>
<textarea class="form-control" formControlName="viewDescription" id="viewDescription" rows="3" placeholder="Enter view description"></textarea>
</div>
</form>
</c-modal-body>
<c-modal-footer>
<button cButton (click)="commitView()" color="primary" [disabled]="!viewForm.valid">Commit</button>
<button [cModalToggle]="AddViewModal.id" cButton color="secondary">Cancel</button>
</c-modal-footer>
</c-modal>
<!-- Existing Update Confirmation Modal -->
<c-modal #ConfirmModal backdrop="static" size="lg" [(visible)]="ConfirmModalVisible" id="ConfirmModal">
<c-modal-header>
<h5 cModalTitle *ngIf="action=='update_mikroman'">Please Confirm Mikroman Update</h5>
@ -246,9 +282,7 @@
</div>
</c-modal-body>
<c-modal-footer>
<button cButton (click)="ConfirmAction()" color="primary"> submit</button>
<button [cModalToggle]="ConfirmModal.id" cButton color="secondary">
Close
</button>
<button cButton (click)="ConfirmAction()" color="primary">Submit</button>
<button [cModalToggle]="ConfirmModal.id" cButton color="secondary">Close</button>
</c-modal-footer>
</c-modal>

View file

@ -0,0 +1,443 @@
// Dashboard Header Styles
.dashboard-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 25px;
border-radius: 12px;
margin-bottom: 25px;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
h2 {
margin: 0;
font-weight: 600;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
}
.dashboard-controls {
.dropdown-toggle {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
border-radius: 25px;
padding: 12px 24px;
font-weight: 500;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
&:hover {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
i {
margin-right: 8px;
}
}
.dropdown-menu {
border: none;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
padding: 8px 0;
margin-top: 8px;
.dropdown-item {
padding: 12px 20px;
font-weight: 500;
transition: all 0.2s ease;
&:hover {
background: #f8f9fa;
color: #495057;
}
i {
margin-right: 10px;
width: 16px;
}
&.text-danger:hover {
background: #dc3545;
color: white;
}
}
}
}
// Dashboard Grid Layout
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
grid-gap: 20px;
grid-auto-rows: minmax(250px, auto);
margin-bottom: 20px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
grid-gap: 15px;
}
}
// Widget Styles
.dashboard-widget {
position: relative;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
}
}
.widget-container {
position: relative;
height: 100%;
transition: all 0.3s ease;
.card {
border: none;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
overflow: hidden;
&:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
padding: 15px 20px;
font-weight: 600;
color: #495057;
}
.card-body {
padding: 20px;
}
}
}
.widget-remove-btn {
position: absolute;
top: 15px;
right: 15px;
z-index: 10;
border-radius: 50%;
width: 32px;
height: 32px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: #dc3545;
color: white;
font-size: 14px;
opacity: 0;
transition: all 0.3s ease;
transform: scale(0.8);
&:hover {
background: #c82333;
transform: scale(1.1);
}
}
.widget-drag-handle {
cursor: move;
color: #6c757d;
transition: color 0.2s ease;
&:hover {
color: #495057;
}
}
// Remove Mode Styles
.remove-mode {
.widget-container {
border: 2px dashed #dc3545;
border-radius: 12px;
background: rgba(220, 53, 69, 0.05);
&:hover {
background: rgba(220, 53, 69, 0.1);
}
}
.widget-remove-btn {
opacity: 1;
transform: scale(1);
}
}
// Custom Widget Styles
.cpu-widget, .memory-widget, .disk-widget, .network-widget {
.progress {
border-radius: 8px;
overflow: hidden;
.progress-bar {
transition: width 0.6s ease;
}
}
canvas {
max-width: 100%;
height: auto;
}
h3 {
color: #495057;
font-weight: 700;
margin: 0;
}
.text-muted {
color: #6c757d !important;
font-size: 0.9rem;
}
}
.network-widget {
.row > div {
padding: 15px;
border-radius: 8px;
margin: 5px;
background: #f8f9fa;
transition: all 0.3s ease;
&:hover {
background: #e9ecef;
transform: translateY(-2px);
}
h4 {
margin: 0;
font-weight: 600;
}
}
}
// Modal Styles
.modal-content {
border: none;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-bottom: none;
padding: 20px 25px;
h5 {
margin: 0;
font-weight: 600;
}
.btn-close {
filter: brightness(0) invert(1);
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
.modal-body {
padding: 25px;
.form-label {
font-weight: 600;
color: #495057;
margin-bottom: 8px;
}
.form-control, .form-select {
border-radius: 8px;
border: 2px solid #e9ecef;
padding: 12px 15px;
transition: all 0.3s ease;
&:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
}
}
.modal-footer {
border-top: 1px solid #e9ecef;
padding: 20px 25px;
.btn {
border-radius: 8px;
padding: 10px 20px;
font-weight: 500;
transition: all 0.3s ease;
&.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
}
&.btn-secondary {
border: 2px solid #6c757d;
color: #6c757d;
background: transparent;
&:hover {
background: #6c757d;
color: white;
}
}
}
}
// Stats Widget Specific Styles
.stats-widget {
.card-footer {
background: #f8f9fa;
border-top: 1px solid #dee2e6;
padding: 20px;
.border-start {
border-left-width: 4px !important;
transition: all 0.3s ease;
&:hover {
transform: translateX(5px);
}
}
}
}
// Traffic Chart Widget
.traffic-widget {
.btn-group {
.btn {
border-radius: 6px;
font-size: 0.875rem;
padding: 8px 12px;
transition: all 0.3s ease;
&.active {
background: #667eea;
border-color: #667eea;
}
}
}
}
// Responsive Design
@media (max-width: 1200px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
}
@media (max-width: 768px) {
.dashboard-header {
padding: 20px;
text-align: center;
.d-flex {
flex-direction: column;
gap: 15px;
}
}
.dashboard-grid {
grid-template-columns: 1fr;
grid-gap: 15px;
}
.widget-container .card {
.card-header {
padding: 12px 15px;
}
.card-body {
padding: 15px;
}
}
}
// Animation Keyframes
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
// Apply animations
.dashboard-widget {
animation: slideInUp 0.5s ease-out;
}
.widget-remove-btn:hover {
animation: pulse 0.3s ease-in-out;
}
// Loading States
.loading-widget {
.card-body {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
.spinner-border {
color: #667eea;
}
}
}
// Success/Error States
.widget-success {
.card {
border-left: 4px solid #28a745;
}
}
.widget-warning {
.card {
border-left: 4px solid #ffc107;
}
}
.widget-error {
.card {
border-left: 4px solid #dc3545;
}
}

View file

@ -1,14 +1,43 @@
import { Component, OnInit } from "@angular/core";
import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
import { UntypedFormControl, UntypedFormGroup, FormBuilder, Validators } from "@angular/forms";
import { dataProvider } from "../../providers/mikrowizard/data";
import { loginChecker } from "../../providers/login_checker";
import { Router } from "@angular/router";
import { formatInTimeZone } from "date-fns-tz";
interface DashboardWidget {
enabled: boolean;
position: {
col: string;
row: string;
};
}
interface CustomWidget {
id: string;
type: string;
title: string;
position: {
col: string;
row: string;
};
data?: any;
}
interface CustomView {
id: string;
name: string;
description?: string;
position: {
col: string;
row: string;
};
}
@Component({
templateUrl: "dashboard.component.html",
styleUrls: ["./dashboard.component.scss"]
})
export class DashboardComponent implements OnInit {
public uid: number;
public uname: string;
@ -16,11 +45,29 @@ export class DashboardComponent implements OnInit {
public copy_msg: any = false;
public ConfirmModalVisible: boolean = false;
public action: string = "";
front_version=require('../../../../package.json').version;
public front_version = require('../../../../package.json').version;
// Dashboard customization properties
public isRemoveMode: boolean = false;
public wizardModalVisible: boolean = false;
public viewModalVisible: boolean = false;
public wizardForm: UntypedFormGroup;
public viewForm: UntypedFormGroup;
// Widget management
public widgets: { [key: string]: DashboardWidget } = {
stats: { enabled: true, position: { col: '1 / -1', row: '1' } },
traffic: { enabled: true, position: { col: '1 / -1', row: '2' } }
};
public customWidgets: CustomWidget[] = [];
public customViews: CustomView[] = [];
constructor(
private data_provider: dataProvider,
private router: Router,
private login_checker: loginChecker
private login_checker: loginChecker,
private fb: FormBuilder
) {
var _self = this;
if (!this.login_checker.isLoggedIn()) {
@ -28,23 +75,33 @@ export class DashboardComponent implements OnInit {
_self.router.navigate(["login"]);
}, 100);
}
this.data_provider.getSessionInfo().then((res) => {
_self.uid = res.uid;
_self.uname = res.name;
_self.tz = res.tz;
const userId = _self.uid;
});
//get datagrid data
function isNotEmpty(value: any): boolean {
return value !== undefined && value !== null && value !== "";
}
// Initialize forms
this.wizardForm = this.fb.group({
wizardType: ['', Validators.required],
wizardTitle: ['', Validators.required]
});
this.viewForm = this.fb.group({
viewName: ['', Validators.required],
viewDescription: ['']
});
// Load saved dashboard configuration
this.loadDashboardConfig();
}
public trafficRadioGroup = new UntypedFormGroup({
trafficRadio: new UntypedFormControl("5m"),
});
public chart_data: any = {};
public Chartoptions = {
responsive: true,
plugins: {
@ -55,7 +112,6 @@ export class DashboardComponent implements OnInit {
let label = context.dataset.label || "";
var res = context.parsed.y;
let unitIndex = 0;
// if (res>8) res /=8;
while (res >= 1024 && unitIndex < units.length - 1) {
res /= 1024;
unitIndex++;
@ -63,19 +119,14 @@ export class DashboardComponent implements OnInit {
switch (context.dataset.unit) {
case "rx":
return "rx/s :" + res.toFixed(3) + " " + units[unitIndex];
break;
case "tx":
return "tx/s :" + res.toFixed(3) + " " + units[unitIndex];
break;
case "rxp":
return "rxp/s :" + context.parsed.y;
break;
case "txp":
return "txp/s :" + context.parsed.y;
break;
default:
return context.parsed.y;
break;
}
},
},
@ -161,6 +212,7 @@ export class DashboardComponent implements OnInit {
},
},
};
public options: any;
public delta: string = "5m";
public stats: any = false;
@ -169,8 +221,159 @@ export class DashboardComponent implements OnInit {
this.options = this.Chartoptions;
this.initStats();
this.initTrafficChart();
this.startDataRefresh();
}
// Dashboard customization methods
showWizardModal(): void {
this.wizardModalVisible = true;
this.wizardForm.reset();
}
showViewModal(): void {
this.viewModalVisible = true;
this.viewForm.reset();
}
toggleRemoveMode(): void {
this.isRemoveMode = !this.isRemoveMode;
}
commitWizard(): void {
if (this.wizardForm.valid) {
const formValue = this.wizardForm.value;
const newWidget: CustomWidget = {
id: this.generateId(),
type: formValue.wizardType,
title: formValue.wizardTitle,
position: this.getNextAvailablePosition(),
data: this.getInitialWidgetData(formValue.wizardType)
};
this.customWidgets.push(newWidget);
this.saveDashboardConfig();
this.wizardModalVisible = false;
this.startWidgetDataRefresh(newWidget);
}
}
commitView(): void {
if (this.viewForm.valid) {
const formValue = this.viewForm.value;
const newView: CustomView = {
id: this.generateId(),
name: formValue.viewName,
description: formValue.viewDescription,
position: this.getNextAvailablePosition()
};
this.customViews.push(newView);
this.saveDashboardConfig();
this.viewModalVisible = false;
}
}
removeWidget(widgetType: string): void {
this.widgets[widgetType].enabled = false;
this.saveDashboardConfig();
}
removeCustomWidget(index: number): void {
this.customWidgets.splice(index, 1);
this.saveDashboardConfig();
}
removeCustomView(index: number): void {
this.customViews.splice(index, 1);
this.saveDashboardConfig();
}
// Helper methods
private generateId(): string {
return 'widget_' + Math.random().toString(36).substr(2, 9);
}
private getNextAvailablePosition(): { col: string; row: string } {
const totalWidgets = Object.keys(this.widgets).filter(key => this.widgets[key].enabled).length +
this.customWidgets.length + this.customViews.length;
const row = Math.floor(totalWidgets / 2) + 3; // Start after default widgets
const col = (totalWidgets % 2) === 0 ? '1 / 2' : '2 / 3';
return { col, row: row.toString() };
}
private getInitialWidgetData(type: string): any {
switch (type) {
case 'cpu':
return { usage: 0 };
case 'memory':
return { usage: 0, used: 0, total: 0 };
case 'disk':
return { usage: 0, used: 0, total: 0 };
case 'network':
return { download: '0 MB/s', upload: '0 MB/s' };
default:
return {};
}
}
private loadDashboardConfig(): void {
const saved = localStorage.getItem('dashboard_config');
if (saved) {
try {
const config = JSON.parse(saved);
this.widgets = { ...this.widgets, ...config.widgets };
this.customWidgets = config.customWidgets || [];
this.customViews = config.customViews || [];
} catch (e) {
console.error('Error loading dashboard config:', e);
}
}
}
private saveDashboardConfig(): void {
const config = {
widgets: this.widgets,
customWidgets: this.customWidgets,
customViews: this.customViews
};
localStorage.setItem('dashboard_config', JSON.stringify(config));
}
private startDataRefresh(): void {
// Refresh widget data every 5 seconds
setInterval(() => {
this.customWidgets.forEach(widget => this.updateWidgetData(widget));
}, 5000);
}
private startWidgetDataRefresh(widget: CustomWidget): void {
this.updateWidgetData(widget);
}
private updateWidgetData(widget: CustomWidget): void {
// Simulate data updates - in real implementation, fetch from API
switch (widget.type) {
case 'cpu':
widget.data.usage = Math.floor(Math.random() * 100);
break;
case 'memory':
widget.data.usage = Math.floor(Math.random() * 100);
widget.data.used = (Math.random() * 16).toFixed(1);
widget.data.total = 16;
break;
case 'disk':
widget.data.usage = Math.floor(Math.random() * 100);
widget.data.used = (Math.random() * 500).toFixed(1);
widget.data.total = 500;
break;
case 'network':
widget.data.download = (Math.random() * 100).toFixed(1) + ' MB/s';
widget.data.upload = (Math.random() * 50).toFixed(1) + ' MB/s';
break;
}
}
// Original methods
initTrafficChart(): void {
var _self = this;
this.data_provider.dashboard_traffic(this.delta).then((res) => {
@ -184,44 +387,45 @@ export class DashboardComponent implements OnInit {
_self.chart_data = { datasets: res["data"]["datasets"], labels: labels };
});
}
initStats() {
initStats(): void {
var _self = this;
this.data_provider.dashboard_stats(true,this.front_version).then((res) => {
this.data_provider.dashboard_stats(true, this.front_version).then((res) => {
_self.stats = res;
});
}
copy_this() {
//show text copy to clipboard for 3 seconds
copy_this(): void {
this.copy_msg = true;
setTimeout(() => {
this.copy_msg = false;
}, 3000);
}
// Traffic Chart
setTrafficPeriod(value: string): void {
this.trafficRadioGroup.setValue({ trafficRadio: value });
this.delta = value;
this.initTrafficChart();
}
showConfirmModal(action: string) {
showConfirmModal(action: string): void {
this.action = action;
this.ConfirmModalVisible = true
this.ConfirmModalVisible = true;
}
ConfirmAction() {
ConfirmAction(): void {
var _self = this;
this.data_provider.apply_update(this.action).then((res) => {
if (res["status"]=='success') {
if (_self.action=='update_mikroman') {
_self.stats['update_inprogress']=true;
}
if (_self.action=='update_mikrofront') {
_self.stats['front_update_inprogress']=true;
}
_self.action="";
_self.ConfirmModalVisible = false;
this.data_provider.apply_update(this.action).then((res) => {
if (res["status"] == 'success') {
if (_self.action == 'update_mikroman') {
_self.stats['update_inprogress'] = true;
}
});
if (_self.action == 'update_mikrofront') {
_self.stats['front_update_inprogress'] = true;
}
_self.action = "";
_self.ConfirmModalVisible = false;
}
});
}
}

View file

@ -1,62 +1,74 @@
<div class="bg-light min-vh-100 d-flex flex-row align-items-center">
<c-container>
<c-row class="justify-content-center">
<c-col md="8">
<c-card-group>
<c-card [ngStyle]="{'width.%': 44}" class="text-white py-5" style="background-color: #303c54;">
<c-card-body class="text-center">
<img style="width: 200px;" src="assets/img/brand/mikrowizard-full.jpg">
</c-card-body>
</c-card>
<c-card class="p-4">
<c-card-body>
<form cForm [formGroup]="loginForm" >
<h1>Login</h1>
<p class="text-medium-emphasis">Sign In to your account</p>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilUser"></svg>
</span>
<input autoComplete="username" cFormControl placeholder="Username" formControlName="username" required #username/>
</c-input-group>
<c-input-group class="mb-1">
<span cInputGroupText>
<svg cIcon name="cilLockLocked"></svg>
</span>
<input
autoComplete="current-password"
cFormControl
placeholder="Password"
type="password"
formControlName="password"
required #password
/>
</c-input-group>
<c-input-group class="mb-1" *ngIf="show_otp">
<span cInputGroupText>
<i class="fa-regular fa-clock"></i>
</span>
<input
cFormControl
placeholder="2FA TOTP key"
formControlName="ga_code"
required #ga_code
/>
</c-input-group>
<code *ngIf="error_msg"><i class="fa-solid fa-triangle-exclamation"></i><small> {{error_msg}}</small></code>
<c-row>
<c-col mb-3 xs="6">
<button type="submit" cButton (click)="onClickSubmit()" class="px-4" color="primary">
Login
</button>
</c-col>
</c-row>
</form>
</c-card-body>
</c-card>
<c-container>
<c-row class="justify-content-center">
<c-col md="8">
<c-card-group>
<c-card [ngStyle]="{'width.%': 44}" class="text-white py-5" style="background-color: #001C41;">
<c-card-body class="text-center d-flex align-items-center justify-content-center">
<img style="width: 250px;" src="assets/Pause GIF Image.gif">
</c-card-body>
</c-card>
<c-card class="p-4">
<c-card-body>
<form cForm [formGroup]="loginForm">
<h1 class="heading-text">Sign in</h1>
<p class="text-medium-emphasis">Don't have an account? <a routerLink="/signup"
class="signup-link">Sign up</a></p>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilUser"></svg>
</span>
<input autoComplete="username" cFormControl placeholder="Email/username field"
formControlName="username" required #username />
</c-input-group>
<c-input-group class="mb-1">
<span cInputGroupText>
<svg cIcon name="cilLockLocked"></svg>
</span>
<input autoComplete="current-password" cFormControl placeholder="Password field"
type="password" formControlName="password" required #password />
</c-input-group>
<c-input-group class="mb-1" *ngIf="show_otp">
<span cInputGroupText>
<i class="fa-regular fa-clock"></i>
</span>
<input cFormControl placeholder="2FA TOTP key" formControlName="ga_code" required
#ga_code />
</c-input-group>
<code
*ngIf="error_msg"><i class="fa-solid fa-triangle-exclamation"></i><small> {{error_msg}}</small></code>
<c-row>
<c-col mb-3 xs="6" class="w-100 mt-3">
<button type="submit" cButton (click)="onClickSubmit()" class="px-4 w-100 btn-signin">
Sign In
</button>
</c-col>
<c-col mb-3 xs="6" class="w-100 mt-3">
<p class="text-center">or continue with</p>
</c-col>
<c-col mb-3 xs="6" class="w-100">
<button
(click)="loginWithOffice365()"
class="px-4 w-100 btn btn-social d-flex gap-2 justify-content-center align-items-center">
<i class="fa-brands fa-microsoft"></i>
<span>Login with Office 365</span>
</button>
</c-col>
<c-col mb-3 xs="6" class="w-100 mt-3">
<button
(click)="loginWithApple()"
class="px-4 w-100 btn btn-social d-flex gap-2 justify-content-center align-items-center">
<i class="fa-brands fa-apple text-dark"></i>
<span>Login with Apple</span>
</button>
</c-col>
</c-row>
</form>
</c-card-body>
</c-card>
</c-card-group>
</c-col>
</c-row>
</c-container>
</div>
</c-card-group>
</c-col>
</c-row>
</c-container>
</div>

View file

@ -0,0 +1,38 @@
$primary-color: #303c54;
$secondary-color: #ffffff;
$border-color: #000000;
$hover-bg-color: #f8f9fa;
$signin-bg-color: #635DFF;
$signin-hover-color: #4a46d1;
.btn-social {
color: $primary-color;
background-color: $secondary-color;
border: 1px solid $border-color;
&:hover {
background-color: $hover-bg-color;
}
}
.signup-link {
color: $primary-color;
text-decoration: none;
font-weight: bold;
}
.heading-text {
font-size: 1.5rem;
font-weight: bold;
color: $primary-color;
}
.btn-signin {
background-color: $signin-bg-color;
color: $secondary-color;
font-weight: bold;
&:hover {
background-color: $signin-hover-color;
}
}

View file

@ -1,15 +1,25 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { dataProvider } from '../../../providers/mikrowizard/data';
import { loginChecker } from '../../../providers/login_checker';
import { Validators, FormControl, FormGroup} from '@angular/forms';
import { MsalService } from '@azure/msal-angular';
import { loginRequest } from '../../../auth/msal-config';
import { appleConfig } from '../../../auth/apple-config';
import appleSignin from 'apple-signin-auth';
declare global {
interface Window {
AppleID: any;
}
}
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent {
export class LoginComponent implements OnInit {
public loginForm: FormGroup;
public forgotForm: FormGroup;
public error_msg: string = "";
@ -24,10 +34,21 @@ export class LoginComponent {
private router: Router,
private data_provider: dataProvider,
private login_checker: loginChecker,
private msalService: MsalService
) {
this.createForm();
};
ngOnInit() {
// Check if user is already logged in with MSAL
if (this.msalService.instance.getActiveAccount()) {
this.handleMsalLogin();
}
// Initialize Apple Sign In
this.initializeAppleSignIn();
}
createForm() {
this.loginForm = new FormGroup({
username: new FormControl(''),
@ -64,4 +85,86 @@ export class LoginComponent {
});
}
loginWithOffice365() {
this.msalService.loginPopup(loginRequest)
.subscribe({
next: (result) => {
this.handleMsalLogin();
},
error: (error) => {
this.error_msg = "Error during Office 365 login: " + error.message;
console.error('MSAL login error:', error);
}
});
}
private handleMsalLogin() {
const account = this.msalService.instance.getActiveAccount();
if (account) {
this.data_provider.loginWithOffice365(account.idTokenClaims)
.then(res => {
if ('uid' in res && res['uid']) {
this.error_msg = "";
this.login_checker.setStatus(true);
this.router.navigate(['/'], { replaceUrl: true });
} else {
this.error_msg = 'Error: Problem with Office 365 login';
}
})
.catch(err => {
this.error_msg = "Connection with backend broken!";
console.error('Backend error:', err);
});
}
}
private initializeAppleSignIn() {
// Load Apple Sign In script
const script = document.createElement('script');
script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js';
script.onload = () => {
window.AppleID.auth.init({
clientId: appleConfig.clientId,
scope: appleConfig.scope,
redirectURI: appleConfig.redirectURI,
state: appleConfig.state,
usePopup: appleConfig.usePopup
});
};
document.body.appendChild(script);
console.log(appleConfig);
}
loginWithApple() {
window.AppleID.auth.signIn()
.then((response: any) => {
// Handle successful sign in
this.handleAppleLogin(response);
})
.catch((error: any) => {
this.error_msg = "Error during Apple login: " + error.message;
console.error('Apple login error:', error);
});
}
private handleAppleLogin(response: any) {
console.log(JSON.stringify(response));
this.data_provider.loginWithApple(response)
.then(res => {
if ('uid' in res && res['uid']) {
this.error_msg = "";
this.login_checker.setStatus(true);
this.router.navigate(['/'], { replaceUrl: true });
} else {
this.error_msg = 'Error: Problem with Apple login';
}
})
.catch(err => {
this.error_msg = "Connection with backend broken!";
console.error('Backend error:', err);
});
}
}

View file

@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { Page404Component } from './page404/page404.component';
import { Page500Component } from './page500/page500.component';
import { LoginComponent } from './login/login.component';
import { SignupComponent } from './signup/signup.component';
const routes: Routes = [
{
@ -26,6 +27,13 @@ const routes: Routes = [
title: 'Login Page'
}
},
{
path: 'signup',
component: SignupComponent,
data: {
title: 'Signup Page'
}
},
];
@NgModule({

View file

@ -3,16 +3,17 @@ import { CommonModule } from '@angular/common';
import { PagesRoutingModule } from './pages-routing.module';
import { LoginComponent } from './login/login.component';
import { SignupComponent } from './signup/signup.component';
import { Page404Component } from './page404/page404.component';
import { Page500Component } from './page500/page500.component';
import { ButtonModule, CardModule, FormModule, GridModule } from '@coreui/angular';
import { IconModule } from '@coreui/icons-angular';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
LoginComponent,
SignupComponent,
Page404Component,
Page500Component
],

View file

@ -0,0 +1,115 @@
<div class="bg-light min-vh-100 d-flex flex-row align-items-center">
<c-container>
<c-row class="justify-content-center">
<c-col md="8">
<c-card-group>
<c-card [ngStyle]="{'width.%': 44}" class="text-white py-5" style="background-color: #001C41;">
<c-card-body class="text-center d-flex align-items-center justify-content-center">
<img style="width: 250px;" src="assets/Pause GIF Image.gif">
</c-card-body>
</c-card>
<c-card class="p-4">
<c-card-body>
<form cForm [formGroup]="signupForm">
<h1 class="heading-text">Create your account</h1>
<p class="text-medium-emphasis">Already have an account? <a routerLink="/login"
class="signup-link">Sign in</a></p>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilUser"></svg>
</span>
<input cFormControl placeholder="Name field" formControlName="username" required />
</c-input-group>
<div *ngIf="submitted && signupForm.get('username')?.errors" class="text-danger mb-3">
<small>Username is required</small>
</div>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilBuilding"></svg>
</span>
<input cFormControl placeholder="Organization field" formControlName="organization"
required />
</c-input-group>
<div *ngIf="submitted && signupForm.get('organization')?.errors"
class="text-danger mb-3">
<small>Organization field is required</small>
</div>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilEnvelopeClosed"></svg>
</span>
<input cFormControl placeholder="Username/email address field"
formControlName="email" required />
</c-input-group>
<div *ngIf="submitted && signupForm.get('email')?.errors" class="text-danger mb-3">
<small *ngIf="signupForm.get('email')?.errors?.['required']">Email is
required</small>
<small *ngIf="signupForm.get('email')?.errors?.['email']">Please enter a valid
email</small>
</div>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilLockLocked"></svg>
</span>
<input cFormControl placeholder="Password field" type="password"
formControlName="password" required />
</c-input-group>
<div *ngIf="submitted && signupForm.get('password')?.errors" class="text-danger mb-3">
<small *ngIf="signupForm.get('password')?.errors?.['required']">Password is
required</small>
<small *ngIf="signupForm.get('password')?.errors?.['minlength']">Password must be at
least 6 characters</small>
</div>
<c-input-group class="mb-3">
<span cInputGroupText>
<svg cIcon name="cilLockLocked"></svg>
</span>
<input cFormControl placeholder="Confirm Password field" type="password"
formControlName="confirmPassword" required />
</c-input-group>
<div *ngIf="submitted && signupForm.get('confirmPassword')?.errors"
class="text-danger mb-3">
<small>Please confirm your password</small>
</div>
<code
*ngIf="error_msg"><i class="fa-solid fa-triangle-exclamation"></i><small> {{error_msg}}</small></code>
<code *ngIf="success_msg"
class="text-success"><i class="fa-solid fa-check"></i><small> {{success_msg}}</small></code>
<c-col mb-3 xs="6" class="w-100 mt-3">
<button type="submit" cButton (click)="onClickSubmit()"
class="px-4 w-100 btn-signin">
Sign Up
</button>
</c-col>
<c-col mb-3 xs="6" class="w-100 mt-3">
<p class="text-center">or continue with</p>
</c-col>
<c-col mb-3 xs="6" class="w-100">
<button
class="px-4 w-100 btn btn-social d-flex gap-2 justify-content-center align-items-center">
<i class="fa-brands fa-microsoft"></i>
<span>Login with Office 365</span>
</button>
</c-col>
<c-col mb-3 xs="6" class="w-100 mt-3">
<button
class="px-4 w-100 btn btn-social d-flex gap-2 justify-content-center align-items-center">
<i class="fa-brands fa-apple text-dark"></i>
<span>Login with Apple</span>
</button>
</c-col>
</form>
</c-card-body>
</c-card>
</c-card-group>
</c-col>
</c-row>
</c-container>
</div>

View file

@ -0,0 +1,38 @@
$primary-color: #303c54;
$secondary-color: #ffffff;
$border-color: #000000;
$hover-bg-color: #f8f9fa;
$signin-bg-color: #635DFF;
$signin-hover-color: #4a46d1;
.signup-link {
color: $primary-color;
text-decoration: none;
font-weight: bold;
}
.heading-text {
font-size: 1.5rem;
font-weight: bold;
color: $primary-color;
}
.btn-signin {
background-color: $signin-bg-color;
color: $secondary-color;
font-weight: bold;
&:hover {
background-color: $signin-hover-color;
}
}
.btn-social {
color: $primary-color;
background-color: $secondary-color;
border: 1px solid $border-color;
&:hover {
background-color: $hover-bg-color;
}
}

View file

@ -0,0 +1,61 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { dataProvider } from '../../../providers/mikrowizard/data';
import { Validators, FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-signup',
templateUrl: './signup.component.html',
styleUrls: ['./signup.component.scss']
})
export class SignupComponent {
public signupForm: FormGroup;
public error_msg: string = "";
public success_msg: string = "";
public submitted = false;
constructor(
private router: Router,
private data_provider: dataProvider,
) {
this.createForm();
}
createForm() {
this.signupForm = new FormGroup({
username: new FormControl('', [Validators.required]),
organization: new FormControl('', [Validators.required]),
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(6)]),
confirmPassword: new FormControl('', [Validators.required])
});
}
onClickSubmit() {
this.submitted = true;
if (this.signupForm.invalid) {
return;
}
const formData = this.signupForm.value;
if (formData.password !== formData.confirmPassword) {
this.error_msg = "Passwords do not match";
return;
}
this.data_provider.signup(formData.username, formData.organization, formData.email, formData.password)
.then(res => {
if (res['status'] === 'success') {
this.success_msg = "Registration successful! Please login.";
setTimeout(() => {
this.router.navigate(['/login']);
}, 2000);
} else {
this.error_msg = res['err'] || 'Registration failed';
}
})
.catch(err => {
this.error_msg = "Connection with backend broken!";
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB