diff --git a/src/layout/MainLayout/Header/index.js b/src/layout/MainLayout/Header/index.js index 5e52026..ca078fa 100644 --- a/src/layout/MainLayout/Header/index.js +++ b/src/layout/MainLayout/Header/index.js @@ -158,6 +158,9 @@ function Header(props) {
  • Gazette Issue
  • +
  • + Audit Log +
  • diff --git a/src/pages/AuditLog/AuditLogSearchForm.js b/src/pages/AuditLog/AuditLogSearchForm.js new file mode 100644 index 0000000..9eff05e --- /dev/null +++ b/src/pages/AuditLog/AuditLogSearchForm.js @@ -0,0 +1,180 @@ +// material-uisubDivision +import { + Button, + // FormControlLabel, + Grid, + TextField, + Typography +} from '@mui/material'; +import MainCard from "components/MainCard"; +import { useForm } from "react-hook-form"; + +import { + // useEffect, + // useState +} from "react"; + +import * as React from "react"; +// import { useNavigate } from "react-router"; +import {PNSPS_BUTTON_THEME} from "themes/buttonConst"; +import {ThemeProvider} from "@emotion/react"; +import * as DateUtils from "utils/DateUtils"; +import * as UrlUtils from "utils/ApiPathConst"; +import * as HttpUtils from "utils/HttpUtils"; +import Loadable from 'components/Loadable'; + +const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); + +// ==============================|| DASHBOARD - DEFAULT ||============================== // + + +const AuditLogSearchForm = ({ applySearch, searchCriteria}) => { + // const navigate = useNavigate(); + + const [minDate, setMinDate] = React.useState(searchCriteria.dateFrom); + const [maxDate, setMaxDate] = React.useState(searchCriteria.dateTo); + const [onDownload, setOnDownload] = React.useState(false); + + const { reset, register, handleSubmit } = useForm() + const onSubmit = (data) => { + + const temp = { + username: data.userName, + modifiedTo: data.modifiedTo, + modifiedFrom: data.modifiedFrom, + }; + applySearch(temp); + }; + + + function resetForm() { + reset(); + } + + function exportExcel() { + setOnDownload(true) + HttpUtils.fileDownload({ + url: UrlUtils.AUDIT_LOG_EXPORT, + onResponse:()=>{ + setOnDownload(false) + }, + onError:()=>{ + setOnDownload(false) + } + }); + } + + return ( + + +
    + + {/*row 1*/} + + + Search + + + {/*row 2*/} + + + + + + + + + { + setMinDate(DateUtils.dateStr(newValue)); + }} + InputLabelProps={{ + shrink: true + }} + /> + + + + { + console.log(newValue) + setMaxDate(DateUtils.dateStr(newValue)); + }} + id="modifiedTo" + type="date" + label="Modified To" + defaultValue={searchCriteria.modifiedTo} + /> + + + + + {/*last row*/} + + + + + + {onDownload? + + : + + } + + + + + + + + + + + + +
    +
    + ); +}; + +export default AuditLogSearchForm; diff --git a/src/pages/AuditLog/AuditLogTable.js b/src/pages/AuditLog/AuditLogTable.js new file mode 100644 index 0000000..71c35a9 --- /dev/null +++ b/src/pages/AuditLog/AuditLogTable.js @@ -0,0 +1,81 @@ +// material-ui +import * as React from 'react'; +import {FiDataGrid} from "components/FiDataGrid"; +import {useEffect} from "react"; +import * as DateUtils from "utils/DateUtils" +import {useTheme} from "@emotion/react"; +import { + // Button, + // Typography, + useMediaQuery +} from '@mui/material'; +// ==============================|| EVENT TABLE ||============================== // + +export default function AuditLogTable({recordList}) { + const [rows, setRows] = React.useState(recordList); + + useEffect(() => { + setRows(recordList); + // console.log(recordList) + }, [recordList]); + const theme = useTheme(); + const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); + + const columns = [ + { + id: 'modified', + field: 'modified', + headerName: 'Date', + flex: isMdOrLg ? 1 : undefined, + valueGetter:(params)=>{ + return DateUtils.datetimeStr(params?.value); + } + }, + { + id: 'modifiedByName', + field: 'modifiedByName', + headerName: 'Username', + flex: isMdOrLg ? 1 : undefined, + }, + { + id: 'tableName', + field: 'tableName', + headerName: 'Table Name', + flex: isMdOrLg ? 1 : undefined, + }, + { + id: 'ref', + field: 'ref', + headerName: 'Ref', + flex: isMdOrLg ? 1 : undefined, + }, + { + id: 'oldData', + field: 'oldData', + headerName: 'Old Data', + flex: isMdOrLg ? 2 : undefined, + }, + { + id: 'newData', + field: 'newData', + headerName: 'New Data', + flex: isMdOrLg ? 2 : undefined, + }, + ]; + + // function handleRowDoubleClick(params) { + // navigate('/user/'+ params.id); + // } + + return ( +
    + 'auto'} + /> +
    + ); +} diff --git a/src/pages/AuditLog/index.js b/src/pages/AuditLog/index.js new file mode 100644 index 0000000..7ddda71 --- /dev/null +++ b/src/pages/AuditLog/index.js @@ -0,0 +1,118 @@ +// material-ui +import { + Grid, + Typography, + Stack, + // Button +} from '@mui/material'; +import MainCard from "components/MainCard"; +import { useEffect, useState } from "react"; +import axios from "axios"; +import { GET_AUDIT_LOG_LIST } from "utils/ApiPathConst"; +import * as React from "react"; +import * as DateUtils from "utils/DateUtils"; + +import Loadable from 'components/Loadable'; +import { lazy } from 'react'; +const LoadingComponent = Loadable(lazy(() => import('../extra-pages/LoadingComponent'))); +const SearchForm = Loadable(lazy(() => import('./AuditLogSearchForm'))); +const EventTable = Loadable(lazy(() => import('./AuditLogTable'))); +import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' + +const BackgroundHead = { + backgroundImage: `url(${titleBackgroundImg})`, + width: '100%', + height: '100%', + backgroundSize: 'contain', + backgroundRepeat: 'no-repeat', + backgroundColor: '#0C489E', + backgroundPosition: 'right' +} +// ==============================|| DASHBOARD - DEFAULT ||============================== // + +const AuditLogPage = () => { + const [record, setRecord] = useState([]); + const [searchCriteria, setSearchCriteria] = React.useState({ + modifiedTo: DateUtils.dateStr(new Date()), + modifiedFrom: DateUtils.dateStr(new Date().setDate(new Date().getDate()-14)), + }); + const [onReady, setOnReady] = useState(false); + const [changelocked, setChangeLocked] = React.useState(false); + + React.useLayoutEffect(() => { + getUserList(); + }, [changelocked]); + + useEffect(() => { + if (record.length > 0) { + setOnReady(true); + } + }, [record]); + + React.useLayoutEffect(() => { + getUserList(); + }, [searchCriteria]); + + function getUserList() { + axios.get(`${GET_AUDIT_LOG_LIST}`, + { params: searchCriteria } + ) + .then((response) => { + if (response.status === 200) { + setRecord(response.data); + } + }) + .catch(error => { + console.log(error); + return false; + }); + } + + function applySearch(input) { + setSearchCriteria(input); + } + + return ( + !onReady ? + + + + + + : + + +
    + + Audit Log + +
    +
    + + {/*row 1*/} + + + + {/*row 2*/} + + + + + + +
    + + ); +}; + +export default AuditLogPage; diff --git a/src/pages/User/DetailsPage_Individual/LoginGrid.js b/src/pages/User/DetailsPage_Individual/LoginGrid.js new file mode 100644 index 0000000..9e367e8 --- /dev/null +++ b/src/pages/User/DetailsPage_Individual/LoginGrid.js @@ -0,0 +1,81 @@ +// material-ui +import * as React from 'react'; +import {FiDataGrid} from "components/FiDataGrid"; +import { + // Button, + // Typography, + useMediaQuery +} from '@mui/material'; +import * as DateUtils from "utils/DateUtils" +// import {useNavigate} from "react-router-dom"; +// import { +// isORGLoggedIn, +// } from "utils/Utils"; +import {useTheme} from "@emotion/react"; +// import {getStatusIntl} from "utils/statusUtils/PublicNoteStatusUtils"; +// import { +// FormattedMessage, +// useIntl} from "react-intl"; +// ==============================|| EVENT TABLE ||============================== // + +export default function LoginGrid({rows}) { + // const navigate = useNavigate() + const theme = useTheme(); + const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); + // const intl = useIntl(); + + // const handleDetailClick = (params) => () => { + // navigate('/publicNotice/'+ params.id); + // }; + + // const remarkHeadername = rows.orgId===null?"我的備註":"Care Of / 我的備註" + + const columns = [ + { + id: 'loginTime', + field: 'loginTime', + headerName: "Date Time", + width: isMdOrLg ? 'auto' : 160, + flex: isMdOrLg ? 1 : undefined, + valueGetter:(params)=>{ + return DateUtils.datetimeStr(params?.value); + } + }, + { + id: 'result', + field: 'result', + headerName: 'Result', + width: isMdOrLg ? 'auto' : 300, + flex: isMdOrLg ? 2 : undefined, + valueGetter:(params)=>{ + let resultMessage = ""; + if (params.row.success>0){ + resultMessage="Success" + } + if (params.row.success==0){ + resultMessage="Fail" + } + if (params.row.success<0){ + resultMessage= params.row.actionType + " by " + (params.row.modifiedBy == null?"multiple times failed login attempt.":params.row.modifiedBy) + } + return resultMessage; + } + }, + ]; + + // function handleRowDoubleClick(params) { + // navigate('/publicNotice/'+ params.id); + // } + + return ( +
    + 'auto'} + /> +
    + ); +} diff --git a/src/pages/User/DetailsPage_Individual/index.js b/src/pages/User/DetailsPage_Individual/index.js index b935105..45a3718 100644 --- a/src/pages/User/DetailsPage_Individual/index.js +++ b/src/pages/User/DetailsPage_Individual/index.js @@ -1,7 +1,8 @@ // material-ui import * as React from "react"; -import { Grid, Typography, Button, Stack } from '@mui/material'; +import { Grid, Typography, Button, Stack, Box, Tab } from '@mui/material'; +import { TabPanel, TabContext, TabList } from '@mui/lab'; import FileList from "components/FileList" import MainCard from "components/MainCard"; import * as HttpUtils from "utils/HttpUtils"; @@ -27,6 +28,7 @@ import * as ComboData from "utils/ComboData"; const LoadingComponent = Loadable(React.lazy(() => import('../../extra-pages/LoadingComponent'))); const UserInformationCard = Loadable(React.lazy(() => import('./UserInformationCard_Individual'))); const UserInformationPubCard = Loadable(React.lazy(() => import('./UserInformationCard_Individual_Pub'))); +const LoginGrid = Loadable(React.lazy(() => import('./LoginGrid'))); import { isGLDLoggedIn, @@ -41,7 +43,9 @@ const UserMaintainPage_Individual = () => { const params = useParams(); const navigate = useNavigate(); const [formData, setFormData] = React.useState({}) + const [loginLogData, setLoginLogData] = React.useState([]) const [isLoading, setLoding] = React.useState(true); + const [selectedTab, setSelectedTab] = React.useState("1"); React.useEffect(() => { if (isORGLoggedIn()){ @@ -51,6 +55,10 @@ const UserMaintainPage_Individual = () => { } }, []); + const handleChange = (event, newValue) => { + setSelectedTab(newValue); + } + const loadData = () => { setLoding(true); if (isGLDLoggedIn()){ @@ -83,6 +91,7 @@ const UserMaintainPage_Individual = () => { response.data["preferLocale"] = getObjectByType(ComboData.Locale, "type", response.data?.preferLocale); setFormData(response.data); + getLoginLogList() } }); } @@ -114,10 +123,23 @@ const UserMaintainPage_Individual = () => { } }; + const getLoginLogList = () => { + HttpUtils.get({ + url: `${UrlUtils.GET_LOGIN_LOG_LIST}`, + params:{ + userId:params.id + }, + onSuccess: function (response) { + // console.log(response) + setLoginLogData(response); + } + }); + } + React.useEffect(() => { setLoding(false); - }, [formData]); + }, [loginLogData]); return ( isLoading ? @@ -173,15 +195,25 @@ const UserMaintainPage_Individual = () => { {isGLDLoggedIn()? - - Files - - - - + + + + + + + + + + + + + +
    diff --git a/src/pages/User/DetailsPage_Organization/LoginGrid.js b/src/pages/User/DetailsPage_Organization/LoginGrid.js new file mode 100644 index 0000000..9e367e8 --- /dev/null +++ b/src/pages/User/DetailsPage_Organization/LoginGrid.js @@ -0,0 +1,81 @@ +// material-ui +import * as React from 'react'; +import {FiDataGrid} from "components/FiDataGrid"; +import { + // Button, + // Typography, + useMediaQuery +} from '@mui/material'; +import * as DateUtils from "utils/DateUtils" +// import {useNavigate} from "react-router-dom"; +// import { +// isORGLoggedIn, +// } from "utils/Utils"; +import {useTheme} from "@emotion/react"; +// import {getStatusIntl} from "utils/statusUtils/PublicNoteStatusUtils"; +// import { +// FormattedMessage, +// useIntl} from "react-intl"; +// ==============================|| EVENT TABLE ||============================== // + +export default function LoginGrid({rows}) { + // const navigate = useNavigate() + const theme = useTheme(); + const isMdOrLg = useMediaQuery(theme.breakpoints.up('md')); + // const intl = useIntl(); + + // const handleDetailClick = (params) => () => { + // navigate('/publicNotice/'+ params.id); + // }; + + // const remarkHeadername = rows.orgId===null?"我的備註":"Care Of / 我的備註" + + const columns = [ + { + id: 'loginTime', + field: 'loginTime', + headerName: "Date Time", + width: isMdOrLg ? 'auto' : 160, + flex: isMdOrLg ? 1 : undefined, + valueGetter:(params)=>{ + return DateUtils.datetimeStr(params?.value); + } + }, + { + id: 'result', + field: 'result', + headerName: 'Result', + width: isMdOrLg ? 'auto' : 300, + flex: isMdOrLg ? 2 : undefined, + valueGetter:(params)=>{ + let resultMessage = ""; + if (params.row.success>0){ + resultMessage="Success" + } + if (params.row.success==0){ + resultMessage="Fail" + } + if (params.row.success<0){ + resultMessage= params.row.actionType + " by " + (params.row.modifiedBy == null?"multiple times failed login attempt.":params.row.modifiedBy) + } + return resultMessage; + } + }, + ]; + + // function handleRowDoubleClick(params) { + // navigate('/publicNotice/'+ params.id); + // } + + return ( +
    + 'auto'} + /> +
    + ); +} diff --git a/src/pages/User/DetailsPage_Organization/index.js b/src/pages/User/DetailsPage_Organization/index.js index 3a795e0..ae3caf8 100644 --- a/src/pages/User/DetailsPage_Organization/index.js +++ b/src/pages/User/DetailsPage_Organization/index.js @@ -1,5 +1,7 @@ // material-ui -import { Grid, Typography, Stack, Box, Button } from '@mui/material'; +import { Grid, Typography, Stack, Box, Button, Tab } from '@mui/material'; +import { TabPanel, TabContext, TabList } from '@mui/lab'; + import { useEffect, useState } from "react"; import * as React from "react"; //import axios from "axios"; @@ -20,6 +22,7 @@ import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' import { useNavigate } from 'react-router-dom'; import * as ComboData from "utils/ComboData"; import {getObjectByValue,getObjectByType} from "utils/CommonFunction"; +const LoginGrid = Loadable(React.lazy(() => import('./LoginGrid'))); const BackgroundHead = { backgroundImage: `url(${titleBackgroundImg})`, @@ -45,32 +48,38 @@ const UserMaintainPage_Organization = () => { const params = useParams(); const navigate = useNavigate(); const [userData, setUserData] = useState({}) + const [loginLogData, setLoginLogData] = React.useState([]) const [orgData, setOrgData] = useState({}) const [isLoading, setLoding] = useState(true); const intl = useIntl(); const { locale } = intl; + const [selectedTab, setSelectedTab] = React.useState("1"); const isPrimaryLocale = locale === 'en' ?"Yes":locale === 'zh-HK' ?"是":"是"; const notPrimaryLocale = locale === 'en' ?"No":locale === 'zh-HK' ?"否":"否"; - const _sx = { - ml: 3, - mb: 3, - mt: 3, - mr: 6, - padding: "4 2 4 2", - boxShadow: 1, - border: 1, - borderColor: '#DDD', - '& .MuiDataGrid-cell': { - borderTop: 1, - borderBottom: 1, - borderColor: "#EEE" - }, - '& .MuiDataGrid-footerContainer': { - border: 1, - borderColor: "#EEE" - } + // const _sx = { + // ml: 3, + // mb: 3, + // mt: 3, + // mr: 6, + // padding: "4 2 4 2", + // boxShadow: 1, + // border: 1, + // borderColor: '#DDD', + // '& .MuiDataGrid-cell': { + // borderTop: 1, + // borderBottom: 1, + // borderColor: "#EEE" + // }, + // '& .MuiDataGrid-footerContainer': { + // border: 1, + // borderColor: "#EEE" + // } + // } + + const handleChange = (event, newValue) => { + setSelectedTab(newValue); } useEffect(() => { @@ -139,7 +148,7 @@ const UserMaintainPage_Organization = () => { response.data["faxNumber"] = response.data.faxNo?.faxNumber; response.data["fax_countryCode"] = response.data.faxNo?.countryCode; - response.data["status"] = response.data?.locked?"locked":response.data?.status; + // response.data["status"] = response.data?.locked?"locked":response.data?.status; response.data["preferLocale"] = getObjectByType(ComboData.Locale, "type", response.data?.preferLocale); //response.data["orgId"] = response.data.brExpiryDate?DateUtils.dateStr(response.data.brExpiryDate):""; @@ -148,6 +157,7 @@ const UserMaintainPage_Organization = () => { // console.log(response.data) setUserData(response.data); setOrgData(response.orgList); + getLoginLogList() } }); } @@ -175,10 +185,24 @@ const UserMaintainPage_Organization = () => { } }; + const getLoginLogList = () => { + HttpUtils.get({ + url: `${UrlUtils.GET_LOGIN_LOG_LIST}`, + params:{ + userId:params.id + }, + onSuccess: function (response) { + // console.log(response) + setLoginLogData(response); + } + }); + } + + useEffect(() => { setLoding(false); - }, [userData]); + }, [loginLogData]); return ( isLoading ? @@ -236,17 +260,27 @@ const UserMaintainPage_Organization = () => { } {isGLDLoggedIn()? - - - - Files - - - - + + + + + + + + + + + + + + + +
    :null diff --git a/src/routes/GLDUserRoutes.js b/src/routes/GLDUserRoutes.js index efa9b76..b317f62 100644 --- a/src/routes/GLDUserRoutes.js +++ b/src/routes/GLDUserRoutes.js @@ -27,6 +27,7 @@ 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 AuditLogPage = Loadable(lazy(() => import('pages/AuditLog/index'))); // ==============================|| MAIN ROUTING ||============================== // @@ -125,6 +126,10 @@ const GLDUserRoutes = { path: '/setting/drImport', element: }, + { + path: '/setting/auditLog', + element: + }, ] }, ] diff --git a/src/utils/ApiPathConst.js b/src/utils/ApiPathConst.js index 8b5311b..ad61c6e 100644 --- a/src/utils/ApiPathConst.js +++ b/src/utils/ApiPathConst.js @@ -69,6 +69,7 @@ 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 AUDIT_LOG_EXPORT = apiPath+'/settings/auditLog-export'; @@ -105,6 +106,7 @@ export const POST_CHECK_APP_EXPRITY_DATE = apiPath+'/application/checkExprityDat //GLD User export const POST_ADMIN_USER_REGISTER = apiPath+'/user/registry'; export const DELETE_USER = apiPath+'/user'; +export const GET_LOGIN_LOG_LIST = apiPath+'/user/loginLogList'; export const GET_PUBLIC_NOTICE_APPLY_DETAIL = apiPath+'/application/application-detail'; export const SET_PUBLIC_NOTICE_GROUP_DETAIL = apiPath+'/application/application-group-detail'; export const SET_PUBLIC_NOTICE_STATUS_NOT_ACCEPT = apiPath+'/application/application-detail-status-not-accept'; @@ -115,6 +117,7 @@ export const SET_PUBLIC_NOTICE_STATUS_RESUBMIT = apiPath+'/application/applicati export const SET_PUBLIC_NOTICE_STATUS_REVIEWED = apiPath+'/application/application-detail-status-reviewed'; export const SET_PUBLIC_NOTICE_STATUS_PUBLISH = apiPath+'/application/application-detail-status-publish'; export const UPDATE_PUBLIC_NOTICE_APPLY_DETAIL = apiPath+'/application/save'; +export const GET_AUDIT_LOG_LIST = apiPath+'/settings/auditLogList'; //gazette export const GET_ISSUE_COMBO = apiPath+'/gazette-issue/combo';//GET