diff --git a/src/layout/MainLayout/Header/index.js b/src/layout/MainLayout/Header/index.js index 335cc59..7f18891 100644 --- a/src/layout/MainLayout/Header/index.js +++ b/src/layout/MainLayout/Header/index.js @@ -516,6 +516,20 @@ function Header(props) { {isGranted("MAINTAIN_SETTING") ? ( <> +
  • + + + HKID Key Migration + + +
  • +
  • + + + User PII Encryption + + +
  • diff --git a/src/pages/Setting/HkidKeyMigration/index.js b/src/pages/Setting/HkidKeyMigration/index.js new file mode 100644 index 0000000..15d091f --- /dev/null +++ b/src/pages/Setting/HkidKeyMigration/index.js @@ -0,0 +1,101 @@ +import * as React from "react"; +import * as HttpUtils from "utils/HttpUtils"; +import * as DateUtils from "utils/DateUtils"; +import * as UrlUtils from "utils/ApiPathConst"; + +import { + Grid, Typography, Button, + Stack, Box, CircularProgress, +} from '@mui/material'; +import { notifyActionError } from 'utils/CommonFunction'; + +import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' + +const formatLog = (responData) => { + if (responData?.msg && !responData?.log) { + return <>{DateUtils.datetimeStr(new Date())}
    Error
    {responData.msg}; + } + const statusColor = responData?.success ? "green" : "red"; + const statusText = responData?.success ? "Success" : "Completed with errors"; + const logText = (responData?.log || []).join("\n"); + return ( + <> + {DateUtils.datetimeStr(new Date())}
    + {responData?.dryRun ? "Dry-run " : ""}{statusText}
    + Total: {responData?.total ?? 0}, OK: {responData?.successCount ?? 0}, Skipped: {responData?.skipped ?? 0}, Failed: {responData?.failed ?? 0}
    + {logText} + + ); +}; + +const HkidKeyMigration = () => { + const [resultStr, setResultStr] = React.useState(""); + const [wait, setWait] = React.useState(false); + + const BackgroundHead = { + backgroundImage: `url(${titleBackgroundImg})`, + width: 'auto', + height: 'auto', + backgroundSize: 'contain', + backgroundRepeat: 'no-repeat', + backgroundColor: '#0C489E', + backgroundPosition: 'right' + }; + + const runMigration = (dryRun) => { + setWait(true); + HttpUtils.post({ + url: `${UrlUtils.HKID_REKEY_MIGRATION}?dryRun=${dryRun}&batchSize=100`, + params: {}, + onSuccess: function (responData) { + setWait(false); + setResultStr(formatLog(responData)); + }, + onError: function () { + setWait(false); + notifyActionError("HKID re-key migration failed"); + } + }); + }; + + return ( + + +
    + + + HKID Key Migration + + +
    +
    + + + + + Re-encrypt identification and checkDigit from the legacy key to the new key. + Ensure Tomcat has both security.hkid-secret and security.hkid-secret-legacy configured before running. + + + + + {wait ? : null} + + + + + + + Result: + {resultStr} + + +
    + ); +}; + +export default HkidKeyMigration; diff --git a/src/pages/Setting/UserPiiEncryption/index.js b/src/pages/Setting/UserPiiEncryption/index.js new file mode 100644 index 0000000..e1e17b9 --- /dev/null +++ b/src/pages/Setting/UserPiiEncryption/index.js @@ -0,0 +1,133 @@ +import * as React from "react"; +import * as HttpUtils from "utils/HttpUtils"; +import * as DateUtils from "utils/DateUtils"; +import * as UrlUtils from "utils/ApiPathConst"; + +import { + Grid, Typography, Button, + Stack, Box, CircularProgress, +} from '@mui/material'; +import { notifyActionError } from 'utils/CommonFunction'; + +import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' + +const formatEncryptLog = (responData) => { + if (responData?.msg && !responData?.log) { + return <>{DateUtils.datetimeStr(new Date())}
    Error
    {responData.msg}; + } + const statusColor = responData?.success ? "green" : "red"; + const statusText = responData?.success ? "Success" : "Completed with errors"; + const logText = (responData?.log || []).join("\n"); + return ( + <> + {DateUtils.datetimeStr(new Date())}
    + {responData?.dryRun ? "Dry-run " : ""}{statusText}
    + Total: {responData?.total ?? 0}, OK: {responData?.successCount ?? 0}, Skipped: {responData?.skipped ?? 0}, Failed: {responData?.failed ?? 0}
    + {logText} + + ); +}; + +const formatVerifyLog = (responData) => { + const tests = responData?.tests || []; + const logText = tests.map(t => `${t.passed ? "PASS" : "FAIL"} - ${t.name}: ${t.message}`).join("\n"); + const statusColor = responData?.success ? "green" : "red"; + return ( + <> + {DateUtils.datetimeStr(new Date())}
    + {responData?.success ? "All tests passed" : "Some tests failed"}
    + Passed: {responData?.passed ?? 0}, Failed: {responData?.failed ?? 0}
    + {logText} + + ); +}; + +const UserPiiEncryption = () => { + const [resultStr, setResultStr] = React.useState(""); + const [wait, setWait] = React.useState(false); + + const BackgroundHead = { + backgroundImage: `url(${titleBackgroundImg})`, + width: 'auto', + height: 'auto', + backgroundSize: 'contain', + backgroundRepeat: 'no-repeat', + backgroundColor: '#0C489E', + backgroundPosition: 'right' + }; + + const runEncrypt = (dryRun) => { + setWait(true); + HttpUtils.post({ + url: `${UrlUtils.USER_PII_ENCRYPT_MIGRATION}?dryRun=${dryRun}&batchSize=100`, + params: {}, + onSuccess: function (responData) { + setWait(false); + setResultStr(formatEncryptLog(responData)); + }, + onError: function () { + setWait(false); + notifyActionError("User PII encryption failed"); + } + }); + }; + + const runVerify = () => { + setWait(true); + HttpUtils.post({ + url: UrlUtils.USER_PII_VERIFY_MIGRATION, + params: {}, + onSuccess: function (responData) { + setWait(false); + setResultStr(formatVerifyLog(responData)); + }, + onError: function () { + setWait(false); + notifyActionError("User PII verification failed"); + } + }); + }; + + return ( + + +
    + + + User PII Encryption + + +
    +
    + + + + + Encrypt enName, chName, mobileNumber, and address for existing user records, then verify data retrieval. + + + + + + {wait ? : null} + + + + + + + Result: + {resultStr} + + +
    + ); +}; + +export default UserPiiEncryption; diff --git a/src/routes/GLDUserRoutes.js b/src/routes/GLDUserRoutes.js index a4a059c..3f893ef 100644 --- a/src/routes/GLDUserRoutes.js +++ b/src/routes/GLDUserRoutes.js @@ -30,6 +30,8 @@ const EmailTemplateDetailPage = Loadable(lazy(() => import('pages/EmailTemplate/ const HolidayPage = Loadable(lazy(() => import('pages/Holiday'))); const GazetteIssuePage = Loadable(lazy(() => import('pages/GazetteIssue/index'))); const DrImport = Loadable(lazy(() => import('pages/Setting/DrImport'))); +const HkidKeyMigration = Loadable(lazy(() => import('pages/Setting/HkidKeyMigration'))); +const UserPiiEncryption = Loadable(lazy(() => import('pages/Setting/UserPiiEncryption'))); const AuditLogPage = Loadable(lazy(() => import('pages/AuditLog/index'))); const ReconReportPage = Loadable(lazy(() => import('pages/Recon'))); const ChangePasswordPage = Loadable(lazy(() => import('pages/User/ChangePasswordPage'))); @@ -190,6 +192,18 @@ const GLDUserRoutes = { element: }:{}, + isGranted("MAINTAIN_SETTING")? + { + path: '/setting/hkidKeyMigration', + element: + }:{}, + + isGranted("MAINTAIN_SETTING")? + { + path: '/setting/userPiiEncryption', + element: + }:{}, + isGranted("MAINTAIN_SETTING")? { path: '/setting/auditLog', diff --git a/src/utils/ApiPathConst.js b/src/utils/ApiPathConst.js index d803a13..161dff9 100644 --- a/src/utils/ApiPathConst.js +++ b/src/utils/ApiPathConst.js @@ -84,6 +84,9 @@ export const GET_FILE_DELETE = apiPath+'/file/delete'; export const DR_EXPORT = apiPath+'/settings/dr/export'; export const DR_IMPORT = apiPath+'/settings/dr/import'; export const OFFLINE_IMPORT = apiPath+'/settings/dr/importOffline'; +export const HKID_REKEY_MIGRATION = apiPath+'/settings/migration/hkid-rekey'; +export const USER_PII_ENCRYPT_MIGRATION = apiPath+'/settings/migration/user-pii-encrypt'; +export const USER_PII_VERIFY_MIGRATION = apiPath+'/settings/migration/user-pii-verify'; export const AUDIT_LOG_EXPORT = apiPath+'/settings/auditLog-export';