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';