Commit 23e076c8 by Ooh-Ao

menu

parent 63435617
......@@ -130,6 +130,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
......@@ -615,6 +616,7 @@
"version": "17.3.12",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz",
"integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "7.23.9",
......@@ -643,6 +645,7 @@
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
"integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
......@@ -673,12 +676,14 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
......@@ -1090,6 +1095,7 @@
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
......@@ -1120,12 +1126,14 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
......@@ -3779,16 +3787,6 @@
"@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz",
......@@ -3798,19 +3796,6 @@
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.9.15",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
......@@ -3885,6 +3870,7 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
......@@ -3902,6 +3888,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
"integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
......@@ -3914,6 +3901,7 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
......@@ -3926,12 +3914,14 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
......@@ -3949,6 +3939,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
......@@ -3964,6 +3955,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
......@@ -4083,13 +4075,6 @@
"rxjs": ">=7.5.0"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT",
"peer": true
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
......@@ -5700,6 +5685,7 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
......@@ -6940,16 +6926,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/jquery": {
"version": "3.5.33",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz",
"integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
......@@ -7076,13 +7052,6 @@
"@types/send": "*"
}
},
"node_modules/@types/sizzle": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz",
"integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==",
"license": "MIT",
"peer": true
},
"node_modules/@types/sockjs": {
"version": "0.3.36",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
......@@ -7612,6 +7581,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
"license": "MIT"
},
"node_modules/anymatch": {
......@@ -7643,6 +7613,7 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
......@@ -7796,6 +7767,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
......@@ -8179,6 +8151,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
......@@ -8227,19 +8200,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/chart.js": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
......@@ -8457,6 +8417,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
......@@ -8595,6 +8556,7 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
......@@ -8773,6 +8735,7 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
......@@ -8787,6 +8750,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
......@@ -8868,6 +8832,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
......@@ -9110,6 +9075,7 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/dir-glob": {
......@@ -9129,6 +9095,7 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT"
},
"node_modules/dns-packet": {
......@@ -9249,6 +9216,7 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/echarts": {
......@@ -9316,6 +9284,7 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
......@@ -9326,6 +9295,7 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
......@@ -10187,6 +10157,7 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
......@@ -10203,6 +10174,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
......@@ -11295,6 +11267,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/isobject": {
......@@ -11402,6 +11375,7 @@
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
......@@ -11455,6 +11429,7 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
......@@ -11944,6 +11919,7 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
"node_modules/loader-runner": {
......@@ -12354,6 +12330,7 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
......@@ -12620,6 +12597,7 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
......@@ -13225,13 +13203,6 @@
"node": ">=0.10.0"
}
},
"node_modules/nouislider": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.8.1.tgz",
"integrity": "sha512-93TweAi8kqntHJSPiSWQ1o/uZ29VWOmal9YKb6KKGGlCkugaNfAupT7o1qTHqdJvNQ7S0su5rO6qRFCjP8fxtw==",
"license": "MIT",
"peer": true
},
"node_modules/npm": {
"version": "10.9.3",
"resolved": "https://registry.npmjs.org/npm/-/npm-10.9.3.tgz",
......@@ -15847,6 +15818,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
......@@ -15856,6 +15828,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
......@@ -16102,6 +16075,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pacote": {
......@@ -16280,6 +16254,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
......@@ -16295,6 +16270,7 @@
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
......@@ -16311,6 +16287,7 @@
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/path-to-regexp": {
......@@ -16361,6 +16338,7 @@
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
......@@ -16597,6 +16575,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
......@@ -16757,6 +16736,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
......@@ -16782,6 +16762,7 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
......@@ -17322,6 +17303,7 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/regenerate": {
......@@ -18147,6 +18129,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
......@@ -18159,6 +18142,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
......@@ -18709,6 +18693,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
......@@ -18736,6 +18721,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
......@@ -18758,6 +18744,7 @@
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
......@@ -18780,6 +18767,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
......@@ -18789,6 +18777,7 @@
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
......@@ -18809,6 +18798,7 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
......@@ -18909,6 +18899,7 @@
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
......@@ -18946,6 +18937,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
......@@ -18958,6 +18950,7 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
......@@ -18975,6 +18968,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
......@@ -19010,6 +19004,7 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
......@@ -19225,6 +19220,7 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
......@@ -19234,6 +19230,7 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
......@@ -19347,6 +19344,7 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": {
......@@ -19407,6 +19405,7 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
......@@ -20595,6 +20594,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
......
......@@ -17,12 +17,14 @@ import { LoginModel } from '../../shared/user-auth.model';
import { HttpClientModule } from '@angular/common/http';
import { Validators } from 'ngx-editor';
import { EmployeeService } from '../../portal-manage/services/employee.service';
import { EmployeeAuthService } from '../../portal-manage/services/employee-auth.service';
import { JwtService } from '../../portal-manage/services/jwt.service';
@Component({
selector: 'app-login',
standalone: true,
imports: [SharedModule, CarouselModule, CommonModule, RouterModule, FormsModule, HttpClientModule, ReactiveFormsModule],
providers: [AuthService, { provide: ToastrService, useClass: ToastrService }],
providers: [AuthService, EmployeeAuthService, JwtService, { provide: ToastrService, useClass: ToastrService }],
templateUrl: './login.component.html',
styleUrl: './login.component.scss'
})
......@@ -43,7 +45,9 @@ export class LoginComponent {
private routes: Router,
private formBuilder: FormBuilder,
public authService: AuthService,
private empServie: EmployeeService
private empServie: EmployeeService,
private employeeAuthService: EmployeeAuthService,
private jwtService: JwtService
) {
localStorage.clear()
}
......@@ -135,22 +139,111 @@ export class LoginComponent {
let loginCredentials: LoginModel = new LoginModel();
loginCredentials.username = this.loginForm.controls['username'].value;
loginCredentials.password = this.loginForm.controls['password'].value;
this.authService.login(loginCredentials).subscribe({
next: (response) => {
console.log('Login successful', response);
// Assuming your API returns a token, store it
// บันทึก token ใน TokenService
if (response.accessToken) {
this.tokenService.saveToken(response.accessToken);
}
// ตรวจสอบว่าเป็น JWT token หรือไม่
if (response.accessToken && this.isJwtToken(response.accessToken)) {
console.log('JWT token detected, processing...');
this.processJwtToken(response.accessToken);
} else {
console.log('Regular token, using traditional auth...');
this.processRegularToken(response);
}
this.routes.navigate(['/home']);
},
error: (error) => {
console.error('Login failed', error);
// Handle login error, e.g., display an error message to the user
this.showLoader = false;
this.error = 'Username หรือ Password ไม่ถูกต้อง';
}
});
}
/**
* ตรวจสอบว่าเป็น JWT token หรือไม่
*/
private isJwtToken(token: string): boolean {
try {
const parts = token.split('.');
return parts.length === 3;
} catch {
return false;
}
}
/**
* ประมวลผล JWT token
*/
private processJwtToken(jwtToken: string): void {
try {
// ตรวจสอบความถูกต้องของ JWT token
const decoded = this.jwtService.decodeToken(jwtToken);
if (decoded.isValid && !decoded.isExpired && decoded.payload) {
console.log('JWT token is valid:', decoded.payload);
// บันทึก JWT token ใน localStorage
localStorage.setItem('jwtToken', jwtToken);
// Authenticate ด้วย JWT
this.employeeAuthService.authenticateByJwtToken(jwtToken).subscribe(jwtEmployee => {
if (jwtEmployee) {
console.log('JWT Employee authenticated successfully:', jwtEmployee);
this.showLoader = false;
} else {
console.error('JWT authentication failed');
this.showLoader = false;
this.error = 'การยืนยันตัวตนล้มเหลว';
}
});
} else {
console.error('Invalid or expired JWT token:', decoded.error);
this.showLoader = false;
this.error = 'Token ไม่ถูกต้องหรือหมดอายุ';
}
} catch (error) {
console.error('JWT processing error:', error);
this.showLoader = false;
this.error = 'เกิดข้อผิดพลาดในการประมวลผล Token';
}
}
/**
* ประมวลผล regular token
*/
private processRegularToken(response: any): void {
try {
// ใช้ traditional authentication
if (response.userId || response.employeeId) {
const employeeId = response.employeeId || response.userId;
this.employeeAuthService.authenticateByEmployeeId(employeeId).subscribe(employee => {
if (employee) {
console.log('Employee authenticated successfully:', employee);
this.showLoader = false;
} else {
console.log('Employee not found, using default authentication');
this.showLoader = false;
}
});
} else {
console.log('No employee ID found, using default authentication');
this.showLoader = false;
}
} catch (error) {
console.error('Regular token processing error:', error);
this.showLoader = false;
}
}
// adminCompanyList(memberId: string) {
// this.empServie.getCompanyAdmin(memberId).subscribe(result => {
// if (result.length > 0) {
......
......@@ -61,6 +61,7 @@
<div class="font-bold text-gray-800 text-lg">{{ userInfo.fullName }}</div>
<div class="text-sm text-gray-600">{{ userInfo.department }}</div>
<div class="text-xs text-gray-500">{{ userInfo.position }}</div>
<div class="text-xs text-blue-600 font-medium">รหัส: {{ userInfo.employeeId }}</div>
</div>
</div>
<button
......
import { Component, OnInit } from '@angular/core';
import { Router, RouterLink, RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { Observable, combineLatest } from 'rxjs';
import { Observable, combineLatest, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { TokenService } from '../../shared/services/token.service';
import { MenuPermissionService } from '../services/menu-permission.service';
import { UserRoleService } from '../services/user-role.service';
import { EmployeeAuthService, Employee } from '../services/employee-auth.service';
import { EmployeeFromJwt } from '../models/jwt-token.model';
interface AppModule {
id: string;
......@@ -25,6 +27,19 @@ interface AppModule {
};
}
interface UserInfo {
fullName: string;
email: string;
department: string;
position: string;
employeeId: string;
phone: string;
picture: string;
username?: string;
userLevel?: string;
companyName?: string;
}
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
......@@ -34,42 +49,149 @@ interface AppModule {
})
export class HomeComponent implements OnInit {
accessibleApps$: Observable<AppModule[]>;
userInfo$: Observable<any>;
userInfo$: Observable<UserInfo>;
currentEmployee$: Observable<Employee | null>;
currentJwtEmployee$: Observable<EmployeeFromJwt | null>;
constructor(
private router: Router,
private tokenService: TokenService,
private menuPermissionService: MenuPermissionService,
private userRoleService: UserRoleService
private userRoleService: UserRoleService,
private employeeAuthService: EmployeeAuthService
) { }
ngOnInit(): void {
this.loadAccessibleApps();
this.loadUserInfo();
this.initializeAuth();
}
private initializeAuth(): void {
// ตรวจสอบ JWT token ก่อน
this.employeeAuthService.authenticateFromStoredJwt().subscribe(jwtEmployee => {
if (jwtEmployee) {
console.log('JWT Employee authenticated:', jwtEmployee);
this.currentJwtEmployee$ = this.employeeAuthService.currentJwtEmployee$;
return;
}
});
// ถ้าไม่มี JWT token ให้ตรวจสอบ auth-token
const authToken = localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
if (authToken) {
this.employeeAuthService.authenticateByToken(authToken).subscribe(employee => {
if (employee) {
console.log('Employee authenticated:', employee);
} else {
console.log('Invalid auth token');
this.logout();
}
});
} else {
// ถ้าไม่มี auth-token ให้ใช้รหัสพนักงานเริ่มต้น
this.employeeAuthService.authenticateByEmployeeId('20180210').subscribe(employee => {
if (employee) {
console.log('Employee authenticated by ID:', employee);
}
});
}
this.currentEmployee$ = this.employeeAuthService.currentEmployee$;
this.currentJwtEmployee$ = this.employeeAuthService.currentJwtEmployee$;
}
private loadAccessibleApps(): void {
this.accessibleApps$ = combineLatest([
this.menuPermissionService.getAccessibleMenus(),
this.menuPermissionService.menuPermissions$
]).pipe(
map(([accessibleMenus, allMenus]) => {
return this.getAppModules().filter(app => {
const menu = this.findMenuByPath(allMenus, app.path);
return menu && menu.isVisible && menu.permissions.view;
});
})
);
// แสดงทุกเมนูก่อน โดยไม่ต้องตรวจสอบสิทธิ์
this.accessibleApps$ = of(this.getAppModules());
// หลังจากนี้สามารถเพิ่มการตรวจสอบสิทธิ์ได้
// this.accessibleApps$ = combineLatest([
// this.employeeAuthService.currentEmployee$,
// this.employeeAuthService.currentJwtEmployee$,
// this.menuPermissionService.getAccessibleMenus(),
// this.menuPermissionService.menuPermissions$
// ]).pipe(
// map(([currentEmployee, currentJwtEmployee, accessibleMenus, allMenus]) => {
// // ตรวจสอบ JWT employee ก่อน
// if (currentJwtEmployee) {
// return this.getAppModules().filter(app => {
// const hasJwtPermission = this.employeeAuthService.hasJwtMenuPermission(app.id, 'view');
// return hasJwtPermission;
// });
// }
// // ถ้าไม่มี JWT ให้ใช้ employee permissions
// if (!currentEmployee) {
// return [];
// }
// return this.getAppModules().filter(app => {
// // ตรวจสอบสิทธิ์จาก employee permissions
// const hasPermission = this.employeeAuthService.hasMenuPermission(app.id, 'view');
// // ตรวจสอบสิทธิ์จาก menu permissions
// const menu = this.findMenuByPath(allMenus, app.path);
// const hasMenuPermission = menu && menu.isVisible && menu.permissions.view;
// return hasPermission || hasMenuPermission;
// });
// })
// );
}
private loadUserInfo(): void {
// Load current user info
this.userInfo$ = this.userRoleService.getUserById('current-user').pipe(
map(user => user || {
fullName: 'ผู้ใช้ปัจจุบัน',
email: 'user@company.com',
department: 'IT',
position: 'System User'
// Load current user info from JWT employee หรือ employee auth service
this.userInfo$ = combineLatest([
this.employeeAuthService.currentJwtEmployee$,
this.employeeAuthService.currentEmployee$
]).pipe(
map(([jwtEmployee, employee]) => {
// ใช้ JWT employee ก่อน
if (jwtEmployee) {
return {
fullName: jwtEmployee.fullName,
email: jwtEmployee.email,
department: jwtEmployee.department,
position: jwtEmployee.position,
employeeId: jwtEmployee.employeeId,
phone: '', // JWT ไม่มี phone
picture: 'default.jpg',
username: jwtEmployee.username,
userLevel: jwtEmployee.userLevel,
companyName: jwtEmployee.companyName
};
}
// ถ้าไม่มี JWT ให้ใช้ employee
if (employee) {
return {
fullName: `${employee.fname} ${employee.lname}`,
email: employee.email,
department: employee.bu1.tdesc,
position: employee.position.tdesc,
employeeId: employee.employeeId,
phone: employee.phone,
picture: employee.picture,
username: '',
userLevel: '',
companyName: ''
};
}
return {
fullName: 'ผู้ใช้ปัจจุบัน',
email: 'user@company.com',
department: 'IT',
position: 'System User',
employeeId: '00000000',
phone: '000-000-0000',
picture: 'default.jpg',
username: '',
userLevel: '',
companyName: ''
};
})
);
}
......@@ -147,7 +269,17 @@ export class HomeComponent implements OnInit {
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'permission-management',
id: 'dashboard',
name: 'dashboard',
displayName: 'Dashboard',
description: 'แดชบอร์ดหลัก',
icon: './assets/images/icons/dashboard.png',
path: '/dashboard',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'permissionManagement',
name: 'permission-management',
displayName: 'Permission Management',
description: 'ระบบจัดการสิทธิ์และบทบาท',
......@@ -157,7 +289,17 @@ export class HomeComponent implements OnInit {
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'menu-permission-management',
id: 'roleManagement',
name: 'role-management',
displayName: 'Role Management',
description: 'ระบบจัดการบทบาท',
icon: './assets/images/icons/users.png',
path: '/portal-manage/role-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'menuPermissionManagement',
name: 'menu-permission-management',
displayName: 'Menu Permission',
description: 'ระบบจัดการสิทธิ์เมนู',
......@@ -167,7 +309,7 @@ export class HomeComponent implements OnInit {
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'user-role-management',
id: 'userManagement',
name: 'user-role-management',
displayName: 'User & Role Management',
description: 'ระบบจัดการผู้ใช้และบทบาท',
......@@ -177,7 +319,7 @@ export class HomeComponent implements OnInit {
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'meeting-booking',
id: 'meetingBooking',
name: 'meeting-booking',
displayName: 'Meeting Booking',
description: 'ระบบจองห้องประชุม',
......@@ -187,7 +329,7 @@ export class HomeComponent implements OnInit {
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'company-management',
id: 'companyManagement',
name: 'company-management',
displayName: 'Company Management',
description: 'ระบบจัดการบริษัท',
......@@ -197,7 +339,17 @@ export class HomeComponent implements OnInit {
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'dashboard-management',
id: 'widgetManagement',
name: 'widget-management',
displayName: 'Widget Management',
description: 'ระบบจัดการวิดเจ็ต',
icon: './assets/images/icons/widget.png',
path: '/portal-manage/widget-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'dashboardManagement',
name: 'dashboard-management',
displayName: 'Dashboard Management',
description: 'ระบบจัดการแดชบอร์ด',
......@@ -223,6 +375,7 @@ export class HomeComponent implements OnInit {
}
logout() {
this.employeeAuthService.logout();
this.tokenService.signOut();
}
......
export interface JwtTokenPayload {
schema: string;
encode: string;
sub: string;
companyName: string;
dbName: string;
roles: string[];
workarea: string;
iss: string;
zmlogin: string;
role_level: string;
employeeid: string;
branch: string;
emp_position: string;
user_role: string;
uid: string;
companyid: string;
actorid: string;
lang: string;
ad: string;
firstlogin: string;
url_myhr: string;
app_name: string;
regionallty: string;
token_zeeme: string;
user_level: string;
fullname: string;
comid: string;
job: string;
user: string;
zm_user: string;
username: string;
memberid: string;
}
export interface JwtTokenHeader {
alg: string;
typ: string;
}
export interface JwtToken {
header: JwtTokenHeader;
payload: JwtTokenPayload;
signature: string;
}
export interface DecodedJwtToken {
isValid: boolean;
isExpired: boolean;
payload: JwtTokenPayload | null;
error?: string;
}
export interface EmployeeFromJwt {
employeeId: string;
fullName: string;
username: string;
email: string;
position: string;
job: string;
department: string;
companyName: string;
companyId: string;
branch: string;
workarea: string;
userLevel: string;
roles: string[];
language: string;
isFirstLogin: boolean;
isAdUser: boolean;
urlMyhr: string;
appName: string;
regionallty: string;
tokenZeeme: string;
zmUser: string;
memberId: string;
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, tap, catchError } from 'rxjs/operators';
import { JwtService } from './jwt.service';
import { EmployeeFromJwt } from '../models/jwt-token.model';
export interface Employee {
employeeId: string;
authToken: string;
bossId: string;
fname: string;
lname: string;
efname: string;
elname: string;
email: string;
phone: string;
bu1: {
bu1id: string;
tdesc: string;
edesc: string;
};
bu2: {
bu2id: string;
tdesc: string;
edesc: string;
parent: string | null;
};
bu3: {
bu3id: string;
tdesc: string;
edesc: string;
parent: string | null;
};
position: {
positionId: string;
tdesc: string;
edesc: string;
consolidate: string;
shortName: string;
};
pl: {
plId: string;
tdesc: string;
edesc: string;
};
picture: string;
busNo: string;
status: {
active: boolean;
lastLogin: string;
loginCount: number;
};
menuPermissions: {
[key: string]: {
view: boolean;
create?: boolean;
edit?: boolean;
delete?: boolean;
export?: boolean;
};
};
}
@Injectable({
providedIn: 'root'
})
export class EmployeeAuthService {
private currentEmployeeSubject = new BehaviorSubject<Employee | null>(null);
public currentEmployee$ = this.currentEmployeeSubject.asObservable();
private currentJwtEmployeeSubject = new BehaviorSubject<EmployeeFromJwt | null>(null);
public currentJwtEmployee$ = this.currentJwtEmployeeSubject.asObservable();
private employeesSubject = new BehaviorSubject<Employee[]>([]);
public employees$ = this.employeesSubject.asObservable();
constructor(
private http: HttpClient,
private jwtService: JwtService
) {
this.loadEmployees();
}
/**
* โหลดข้อมูลพนักงานทั้งหมด
*/
private loadEmployees(): void {
this.http.get<Employee[]>('/assets/data/employeelist.json')
.pipe(
tap(employees => this.employeesSubject.next(employees)),
catchError(error => {
console.error('Error loading employees:', error);
return of([]);
})
)
.subscribe();
}
/**
* ตรวจสอบ auth-token และดึงข้อมูลพนักงาน
*/
authenticateByToken(authToken: string): Observable<Employee | null> {
return this.employees$.pipe(
map(employees => {
const employee = employees.find(emp => emp.authToken === authToken);
if (employee) {
this.currentEmployeeSubject.next(employee);
return employee;
}
return null;
})
);
}
/**
* ตรวจสอบ JWT token และดึงข้อมูลพนักงาน
*/
authenticateByJwtToken(jwtToken: string): Observable<EmployeeFromJwt | null> {
try {
const jwtEmployee = this.jwtService.getEmployeeFromToken(jwtToken);
if (jwtEmployee) {
this.currentJwtEmployeeSubject.next(jwtEmployee);
// บันทึก token ใน localStorage
localStorage.setItem('jwtToken', jwtToken);
return of(jwtEmployee);
} else {
this.currentJwtEmployeeSubject.next(null);
return of(null);
}
} catch (error) {
console.error('JWT authentication error:', error);
this.currentJwtEmployeeSubject.next(null);
return of(null);
}
}
/**
* ตรวจสอบ JWT token จาก localStorage
*/
authenticateFromStoredJwt(): Observable<EmployeeFromJwt | null> {
const storedToken = localStorage.getItem('jwtToken') || sessionStorage.getItem('jwtToken');
if (storedToken) {
return this.authenticateByJwtToken(storedToken);
}
return of(null);
}
/**
* ตรวจสอบ auth-token โดยใช้รหัสพนักงาน
*/
authenticateByEmployeeId(employeeId: string): Observable<Employee | null> {
return this.employees$.pipe(
map(employees => {
const employee = employees.find(emp => emp.employeeId === employeeId);
if (employee) {
this.currentEmployeeSubject.next(employee);
return employee;
}
return null;
})
);
}
/**
* ดึงข้อมูลพนักงานปัจจุบัน
*/
getCurrentEmployee(): Employee | null {
return this.currentEmployeeSubject.value;
}
/**
* ดึงข้อมูลพนักงานจาก JWT
*/
getCurrentJwtEmployee(): EmployeeFromJwt | null {
return this.currentJwtEmployeeSubject.value;
}
/**
* ตรวจสอบสิทธิ์การเข้าถึงเมนูจาก JWT
*/
hasJwtMenuPermission(menuKey: string, action: string = 'view'): boolean {
const currentJwtEmployee = this.getCurrentJwtEmployee();
if (!currentJwtEmployee) {
return false;
}
// ตรวจสอบจาก user level
const userLevel = currentJwtEmployee.userLevel;
const isAdmin = userLevel.includes('Admin') || userLevel.includes('Manager') || userLevel.includes('Executive');
if (isAdmin) {
return true; // Admin มีสิทธิ์ทุกอย่าง
}
// ตรวจสอบจาก roles
const roles = currentJwtEmployee.roles;
if (roles.includes('ADMIN') || roles.includes('MANAGER')) {
return true;
}
// ตรวจสอบสิทธิ์ตาม menu
const menuPermissions = this.getJwtMenuPermissions();
if (menuPermissions[menuKey]) {
return menuPermissions[menuKey][action as keyof typeof menuPermissions[typeof menuKey]] === true;
}
return false;
}
/**
* ดึงสิทธิ์การเข้าถึงเมนูจาก JWT
*/
getJwtMenuPermissions(): { [key: string]: any } {
const currentJwtEmployee = this.getCurrentJwtEmployee();
if (!currentJwtEmployee) {
return {};
}
const userLevel = currentJwtEmployee.userLevel;
const isAdmin = userLevel.includes('Admin') || userLevel.includes('Manager') || userLevel.includes('Executive');
if (isAdmin) {
// Admin มีสิทธิ์ทุกอย่าง
return {
dashboard: { view: true, create: true, edit: true, delete: true, export: true },
userManagement: { view: true, create: true, edit: true, delete: true, export: true },
roleManagement: { view: true, create: true, edit: true, delete: true, export: true },
permissionManagement: { view: true, create: true, edit: true, delete: true, export: true },
menuPermissionManagement: { view: true, create: true, edit: true, delete: true, export: true },
meetingBooking: { view: true, create: true, edit: true, delete: true, export: true },
companyManagement: { view: true, create: true, edit: true, delete: true, export: true },
widgetManagement: { view: true, create: true, edit: true, delete: true, export: true },
dashboardManagement: { view: true, create: true, edit: true, delete: true, export: true }
};
}
// Employee มีสิทธิ์จำกัด
return {
dashboard: { view: true, create: false, edit: false, delete: false, export: false },
userManagement: { view: true, create: false, edit: false, delete: false, export: false },
roleManagement: { view: false, create: false, edit: false, delete: false, export: false },
permissionManagement: { view: false, create: false, edit: false, delete: false, export: false },
menuPermissionManagement: { view: false, create: false, edit: false, delete: false, export: false },
meetingBooking: { view: true, create: true, edit: true, delete: false, export: false },
companyManagement: { view: false, create: false, edit: false, delete: false, export: false },
widgetManagement: { view: false, create: false, edit: false, delete: false, export: false },
dashboardManagement: { view: false, create: false, edit: false, delete: false, export: false }
};
}
/**
* ตรวจสอบสิทธิ์การเข้าถึงเมนู
*/
hasMenuPermission(menuKey: string, action: string = 'view'): boolean {
const currentEmployee = this.getCurrentEmployee();
if (!currentEmployee || !currentEmployee.menuPermissions[menuKey]) {
return false;
}
const permission = currentEmployee.menuPermissions[menuKey];
return permission[action as keyof typeof permission] === true;
}
/**
* ดึงสิทธิ์การเข้าถึงเมนูทั้งหมด
*/
getMenuPermissions(): { [key: string]: any } | null {
const currentEmployee = this.getCurrentEmployee();
return currentEmployee ? currentEmployee.menuPermissions : null;
}
/**
* ตรวจสอบว่าพนักงานมีสิทธิ์เข้าถึงโมดูลหรือไม่
*/
canAccessModule(moduleKey: string): boolean {
return this.hasMenuPermission(moduleKey, 'view');
}
/**
* ดึงรายการโมดูลที่เข้าถึงได้
*/
getAccessibleModules(): string[] {
const permissions = this.getMenuPermissions();
if (!permissions) return [];
return Object.keys(permissions).filter(moduleKey =>
this.canAccessModule(moduleKey)
);
}
/**
* ออกจากระบบ
*/
logout(): void {
this.currentEmployeeSubject.next(null);
this.currentJwtEmployeeSubject.next(null);
localStorage.removeItem('jwtToken');
sessionStorage.removeItem('jwtToken');
}
/**
* ตรวจสอบสถานะการล็อกอิน
*/
isLoggedIn(): boolean {
return this.currentEmployeeSubject.value !== null;
}
/**
* ดึงข้อมูลพนักงานตามรหัสพนักงาน
*/
getEmployeeById(employeeId: string): Observable<Employee | null> {
return this.employees$.pipe(
map(employees => employees.find(emp => emp.employeeId === employeeId) || null)
);
}
/**
* ดึงข้อมูลพนักงานทั้งหมด
*/
getAllEmployees(): Observable<Employee[]> {
return this.employees$;
}
/**
* อัปเดตข้อมูลพนักงาน
*/
updateEmployee(employeeId: string, updatedData: Partial<Employee>): Observable<boolean> {
return this.employees$.pipe(
map(employees => {
const index = employees.findIndex(emp => emp.employeeId === employeeId);
if (index !== -1) {
employees[index] = { ...employees[index], ...updatedData };
this.employeesSubject.next([...employees]);
return true;
}
return false;
})
);
}
/**
* ตรวจสอบรหัสพนักงานที่ระบุ
*/
validateEmployeeIds(employeeIds: string[]): Observable<{ valid: string[], invalid: string[] }> {
return this.employees$.pipe(
map(employees => {
const validIds: string[] = [];
const invalidIds: string[] = [];
employeeIds.forEach(id => {
const exists = employees.some(emp => emp.employeeId === id);
if (exists) {
validIds.push(id);
} else {
invalidIds.push(id);
}
});
return { valid: validIds, invalid: invalidIds };
})
);
}
}
import { Injectable } from '@angular/core';
import { JwtTokenPayload, DecodedJwtToken, EmployeeFromJwt } from '../models/jwt-token.model';
@Injectable({
providedIn: 'root'
})
export class JwtService {
constructor() { }
/**
* Decode JWT token โดยไม่ต้องใช้ library ภายนอก
*/
decodeToken(token: string): DecodedJwtToken {
try {
if (!token) {
return {
isValid: false,
isExpired: false,
payload: null,
error: 'Token is empty'
};
}
// แยก JWT token เป็น 3 ส่วน
const parts = token.split('.');
if (parts.length !== 3) {
return {
isValid: false,
isExpired: false,
payload: null,
error: 'Invalid token format'
};
}
// Decode header
const header = this.base64UrlDecode(parts[0]);
const decodedHeader = JSON.parse(header);
// Decode payload
const payload = this.base64UrlDecode(parts[1]);
const decodedPayload: JwtTokenPayload = JSON.parse(payload);
// ตรวจสอบว่า token หมดอายุหรือไม่
const isExpired = this.isTokenExpired(decodedPayload);
return {
isValid: true,
isExpired: isExpired,
payload: decodedPayload,
error: isExpired ? 'Token expired' : undefined
};
} catch (error) {
return {
isValid: false,
isExpired: false,
payload: null,
error: `Token decode error: ${error}`
};
}
}
/**
* Decode base64url string
*/
private base64UrlDecode(str: string): string {
// แปลง base64url เป็น base64
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
// เพิ่ม padding ถ้าจำเป็น
while (base64.length % 4) {
base64 += '=';
}
// Decode base64
return decodeURIComponent(
atob(base64)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
}
/**
* ตรวจสอบว่า token หมดอายุหรือไม่
*/
private isTokenExpired(payload: JwtTokenPayload): boolean {
// ตรวจสอบ exp claim ถ้ามี
if ('exp' in payload) {
const exp = (payload as any).exp;
if (exp) {
const currentTime = Math.floor(Date.now() / 1000);
return exp < currentTime;
}
}
// ตรวจสอบ iat claim ถ้ามี
if ('iat' in payload) {
const iat = (payload as any).iat;
if (iat) {
const currentTime = Math.floor(Date.now() / 1000);
// ถ้า token อายุเกิน 24 ชั่วโมงให้ถือว่า expired
const maxAge = 24 * 60 * 60; // 24 hours
return (currentTime - iat) > maxAge;
}
}
return false;
}
/**
* แปลง JWT payload เป็น Employee object
*/
convertToEmployee(payload: JwtTokenPayload): EmployeeFromJwt {
return {
employeeId: payload.employeeid || payload.uid || '',
fullName: payload.fullname || '',
username: payload.username || payload.user || '',
email: `${payload.username || payload.user || ''}@${payload.companyName || 'company.com'}`,
position: payload.emp_position || '',
job: payload.job || '',
department: this.extractDepartmentFromPosition(payload.emp_position || ''),
companyName: payload.companyName || '',
companyId: payload.companyid || '',
branch: payload.branch || '',
workarea: payload.workarea || '',
userLevel: payload.user_level || '',
roles: payload.roles || [],
language: payload.lang || 'tha',
isFirstLogin: payload.firstlogin === 'true',
isAdUser: payload.ad === 'true',
urlMyhr: payload.url_myhr || '',
appName: payload.app_name || '',
regionallty: payload.regionallty || '',
tokenZeeme: payload.token_zeeme || '',
zmUser: payload.zm_user || '',
memberId: payload.memberid || ''
};
}
/**
* แยกชื่อแผนกจากตำแหน่ง
*/
private extractDepartmentFromPosition(position: string): string {
if (!position) return '';
// ตัวอย่าง: P-RD-2201 -> RD (Research & Development)
const parts = position.split('-');
if (parts.length >= 2) {
const deptCode = parts[1];
const deptMap: { [key: string]: string } = {
'RD': 'ฝ่ายวิจัยและพัฒนา',
'IT': 'ฝ่ายเทคโนโลยีสารสนเทศ',
'HR': 'ฝ่ายทรัพยากรบุคคล',
'EX': 'ฝ่ายบริหาร',
'AC': 'ฝ่ายบัญชี',
'MK': 'ฝ่ายการตลาด',
'SL': 'ฝ่ายขาย',
'OP': 'ฝ่ายปฏิบัติการ'
};
return deptMap[deptCode] || deptCode;
}
return position;
}
/**
* ตรวจสอบว่า token ถูกต้องและไม่หมดอายุ
*/
isTokenValid(token: string): boolean {
const decoded = this.decodeToken(token);
return decoded.isValid && !decoded.isExpired;
}
/**
* ดึงข้อมูลพนักงานจาก token
*/
getEmployeeFromToken(token: string): EmployeeFromJwt | null {
const decoded = this.decodeToken(token);
if (decoded.isValid && !decoded.isExpired && decoded.payload) {
return this.convertToEmployee(decoded.payload);
}
return null;
}
/**
* ตรวจสอบสิทธิ์จาก roles ใน token
*/
hasRole(token: string, role: string): boolean {
const decoded = this.decodeToken(token);
if (decoded.isValid && decoded.payload) {
return decoded.payload.roles.includes(role);
}
return false;
}
/**
* ตรวจสอบ user level
*/
getUserLevel(token: string): string {
const decoded = this.decodeToken(token);
if (decoded.isValid && decoded.payload) {
return decoded.payload.user_level || '';
}
return '';
}
/**
* ตรวจสอบว่าเป็น admin หรือไม่
*/
isAdmin(token: string): boolean {
const userLevel = this.getUserLevel(token);
return userLevel.includes('Admin') || userLevel.includes('Manager') || userLevel.includes('Executive');
}
/**
* ตรวจสอบว่าเป็น employee หรือไม่
*/
isEmployee(token: string): boolean {
const userLevel = this.getUserLevel(token);
return userLevel.includes('Emp-User') || userLevel.includes('Employee');
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment