1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-04 00:18:40 +01:00

refactor: rename sign-on log to login history (#3245)

Renames `sign-on log` to `login history`.
This commit is contained in:
Nuno Góis 2023-03-02 13:49:50 +00:00 committed by GitHub
parent 456eb04591
commit ea83849cd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 94 additions and 75 deletions

View File

@ -3,16 +3,16 @@ import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { SignOnLogTable } from './SignOnLogTable/SignOnLogTable'; import { LoginHistoryTable } from './LoginHistoryTable/LoginHistoryTable';
export const SignOnLog = () => { export const LoginHistory = () => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
return ( return (
<div> <div>
<ConditionallyRender <ConditionallyRender
condition={hasAccess(ADMIN)} condition={hasAccess(ADMIN)}
show={<SignOnLogTable />} show={<LoginHistoryTable />}
elseShow={<AdminAlert />} elseShow={<AdminAlert />}
/> />
</div> </div>

View File

@ -8,13 +8,13 @@ const StyledBox = styled(Box)(() => ({
justifyContent: 'center', justifyContent: 'center',
})); }));
interface ISignOnLogActionsCellProps { interface ILoginHistoryActionsCellProps {
onDelete: (event: React.SyntheticEvent) => void; onDelete: (event: React.SyntheticEvent) => void;
} }
export const SignOnLogActionsCell = ({ export const LoginHistoryActionsCell = ({
onDelete, onDelete,
}: ISignOnLogActionsCellProps) => { }: ILoginHistoryActionsCellProps) => {
return ( return (
<StyledBox> <StyledBox>
<PermissionIconButton <PermissionIconButton

View File

@ -1,28 +1,28 @@
import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue } from 'component/common/Dialogue/Dialogue';
interface IServiceAccountDeleteAllDialogProps { interface ILoginHistoryDeleteAllDialogProps {
open: boolean; open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>; setOpen: React.Dispatch<React.SetStateAction<boolean>>;
onConfirm: () => void; onConfirm: () => void;
} }
export const SignOnLogDeleteAllDialog = ({ export const LoginHistoryDeleteAllDialog = ({
open, open,
setOpen, setOpen,
onConfirm, onConfirm,
}: IServiceAccountDeleteAllDialogProps) => ( }: ILoginHistoryDeleteAllDialogProps) => (
<Dialogue <Dialogue
title="Clear sign-on log?" title="Clear login history?"
open={open} open={open}
primaryButtonText="Clear sign-on log" primaryButtonText="Clear login history"
secondaryButtonText="Cancel" secondaryButtonText="Cancel"
onClick={onConfirm} onClick={onConfirm}
onClose={() => { onClose={() => {
setOpen(false); setOpen(false);
}} }}
> >
You are about to clear the sign-on log. You are about to clear the login history.
<br /> <br />
This will delete all the sign-on events. This will delete all the login events.
</Dialogue> </Dialogue>
); );

View File

@ -1,19 +1,19 @@
import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { ISignOnEvent } from 'interfaces/signOnEvent'; import { ILoginEvent } from 'interfaces/loginEvent';
interface IServiceAccountDeleteDialogProps { interface ILoginHistoryDeleteDialogProps {
event?: ISignOnEvent; event?: ILoginEvent;
open: boolean; open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>; setOpen: React.Dispatch<React.SetStateAction<boolean>>;
onConfirm: (event: ISignOnEvent) => void; onConfirm: (event: ILoginEvent) => void;
} }
export const SignOnLogDeleteDialog = ({ export const LoginHistoryDeleteDialog = ({
event, event,
open, open,
setOpen, setOpen,
onConfirm, onConfirm,
}: IServiceAccountDeleteDialogProps) => ( }: ILoginHistoryDeleteDialogProps) => (
<Dialogue <Dialogue
title="Delete event?" title="Delete event?"
open={open} open={open}

View File

@ -2,7 +2,7 @@ import { VFC } from 'react';
import { Box, styled } from '@mui/material'; import { Box, styled } from '@mui/material';
import { Highlighter } from 'component/common/Highlighter/Highlighter'; import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { ISignOnEvent } from 'interfaces/signOnEvent'; import { ILoginEvent } from 'interfaces/loginEvent';
import { Badge } from 'component/common/Badge/Badge'; import { Badge } from 'component/common/Badge/Badge';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
@ -11,17 +11,16 @@ const StyledBox = styled(Box)(() => ({
justifyContent: 'center', justifyContent: 'center',
})); }));
interface ISignOnLogSuccessfulCellProps { interface ILoginHistorySuccessfulCellProps {
row: { row: {
original: ISignOnEvent; original: ILoginEvent;
}; };
value: boolean; value: boolean;
} }
export const SignOnLogSuccessfulCell: VFC<ISignOnLogSuccessfulCellProps> = ({ export const LoginHistorySuccessfulCell: VFC<
row, ILoginHistorySuccessfulCellProps
value, > = ({ row, value }) => {
}) => {
const { searchQuery } = useSearchHighlightContext(); const { searchQuery } = useSearchHighlightContext();
if (value) if (value)

View File

@ -16,17 +16,17 @@ import { Search } from 'component/common/Search/Search';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
import { useSearch } from 'hooks/useSearch'; import { useSearch } from 'hooks/useSearch';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { useSignOnLog } from 'hooks/api/getters/useSignOnLog/useSignOnLog'; import { useLoginHistory } from 'hooks/api/getters/useLoginHistory/useLoginHistory';
import { SignOnLogSuccessfulCell } from './SignOnLogSuccessfulCell/SignOnLogSuccessfulCell'; import { LoginHistorySuccessfulCell } from './LoginHistorySuccessfulCell/LoginHistorySuccessfulCell';
import { ISignOnEvent } from 'interfaces/signOnEvent'; import { ILoginEvent } from 'interfaces/loginEvent';
import { SignOnLogActionsCell } from './SignOnLogActionsCell/SignOnLogActionsCell'; import { LoginHistoryActionsCell } from './LoginHistoryActionsCell/LoginHistoryActionsCell';
import { SignOnLogDeleteDialog } from './SignOnLogDeleteDialog/SignOnLogDeleteDialog'; import { LoginHistoryDeleteDialog } from './LoginHistoryDeleteDialog/LoginHistoryDeleteDialog';
import { useSignOnLogApi } from 'hooks/api/actions/useSignOnLogApi/useSignOnLogApi'; import { useLoginHistoryApi } from 'hooks/api/actions/useLoginHistoryApi/useLoginHistoryApi';
import { formatDateYMDHMS } from 'utils/formatDate'; import { formatDateYMDHMS } from 'utils/formatDate';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { createLocalStorage } from 'utils/createLocalStorage'; import { createLocalStorage } from 'utils/createLocalStorage';
import { Delete, Download } from '@mui/icons-material'; import { Delete, Download } from '@mui/icons-material';
import { SignOnLogDeleteAllDialog } from './SignOnLogDeleteAllDialog/SignOnLogDeleteAllDialog'; import { LoginHistoryDeleteAllDialog } from './LoginHistoryDeleteAllDialog/LoginHistoryDeleteAllDialog';
export type PageQueryType = Partial< export type PageQueryType = Partial<
Record<'sort' | 'order' | 'search', string> Record<'sort' | 'order' | 'search', string>
@ -35,7 +35,7 @@ export type PageQueryType = Partial<
const defaultSort: SortingRule<string> = { id: 'created_at' }; const defaultSort: SortingRule<string> = { id: 'created_at' };
const { value: storedParams, setValue: setStoredParams } = createLocalStorage( const { value: storedParams, setValue: setStoredParams } = createLocalStorage(
'SignOnLogTable:v1', 'LoginHistoryTable:v1',
defaultSort defaultSort
); );
@ -46,11 +46,11 @@ const AUTH_TYPE_LABEL: { [key: string]: string } = {
google: 'Google', google: 'Google',
}; };
export const SignOnLogTable = () => { export const LoginHistoryTable = () => {
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { events, loading, refetch } = useSignOnLog(); const { events, loading, refetch } = useLoginHistory();
const { removeEvent, removeAllEvents, downloadCSV } = useSignOnLogApi(); const { removeEvent, removeAllEvents, downloadCSV } = useLoginHistoryApi();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const [initialState] = useState(() => ({ const [initialState] = useState(() => ({
@ -67,11 +67,11 @@ export const SignOnLogTable = () => {
})); }));
const [searchValue, setSearchValue] = useState(initialState.globalFilter); const [searchValue, setSearchValue] = useState(initialState.globalFilter);
const [selectedEvent, setSelectedEvent] = useState<ISignOnEvent>(); const [selectedEvent, setSelectedEvent] = useState<ILoginEvent>();
const [deleteOpen, setDeleteOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false);
const [deleteAllOpen, setDeleteAllOpen] = useState(false); const [deleteAllOpen, setDeleteAllOpen] = useState(false);
const onDeleteConfirm = async (event: ISignOnEvent) => { const onDeleteConfirm = async (event: ILoginEvent) => {
try { try {
await removeEvent(event.id); await removeEvent(event.id);
setToastData({ setToastData({
@ -89,7 +89,7 @@ export const SignOnLogTable = () => {
try { try {
await removeAllEvents(); await removeAllEvents();
setToastData({ setToastData({
title: `Log has been cleared`, title: `History has been cleared`,
type: 'success', type: 'success',
}); });
refetch(); refetch();
@ -122,7 +122,7 @@ export const SignOnLogTable = () => {
}, },
{ {
Header: 'Authentication', Header: 'Authentication',
accessor: (event: ISignOnEvent) => accessor: (event: ILoginEvent) =>
AUTH_TYPE_LABEL[event.auth_type] || event.auth_type, AUTH_TYPE_LABEL[event.auth_type] || event.auth_type,
width: 150, width: 150,
maxWidth: 150, maxWidth: 150,
@ -140,7 +140,7 @@ export const SignOnLogTable = () => {
Header: 'Success', Header: 'Success',
accessor: 'successful', accessor: 'successful',
align: 'center', align: 'center',
Cell: SignOnLogSuccessfulCell, Cell: LoginHistorySuccessfulCell,
filterName: 'success', filterName: 'success',
filterParsing: (value: boolean) => value.toString(), filterParsing: (value: boolean) => value.toString(),
}, },
@ -149,7 +149,7 @@ export const SignOnLogTable = () => {
id: 'Actions', id: 'Actions',
align: 'center', align: 'center',
Cell: ({ row: { original: event } }: any) => ( Cell: ({ row: { original: event } }: any) => (
<SignOnLogActionsCell <LoginHistoryActionsCell
onDelete={() => { onDelete={() => {
setSelectedEvent(event); setSelectedEvent(event);
setDeleteOpen(true); setDeleteOpen(true);
@ -238,7 +238,7 @@ export const SignOnLogTable = () => {
isLoading={loading} isLoading={loading}
header={ header={
<PageHeader <PageHeader
title={`Sign-on log (${rows.length})`} title={`Login history (${rows.length})`}
actions={ actions={
<> <>
<ConditionallyRender <ConditionallyRender
@ -261,7 +261,7 @@ export const SignOnLogTable = () => {
show={<PageHeader.Divider />} show={<PageHeader.Divider />}
/> />
<Tooltip <Tooltip
title="Download sign-on log" title="Download login history"
arrow arrow
> >
<IconButton onClick={downloadCSV}> <IconButton onClick={downloadCSV}>
@ -269,7 +269,7 @@ export const SignOnLogTable = () => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
title="Clear sign-on log" title="Clear login history"
arrow arrow
> >
<IconButton <IconButton
@ -314,26 +314,26 @@ export const SignOnLogTable = () => {
condition={searchValue?.length > 0} condition={searchValue?.length > 0}
show={ show={
<TablePlaceholder> <TablePlaceholder>
No sign-on events found matching &ldquo; No login events found matching &ldquo;
{searchValue} {searchValue}
&rdquo; &rdquo;
</TablePlaceholder> </TablePlaceholder>
} }
elseShow={ elseShow={
<TablePlaceholder> <TablePlaceholder>
No sign-on events available. No login events available.
</TablePlaceholder> </TablePlaceholder>
} }
/> />
} }
/> />
<SignOnLogDeleteDialog <LoginHistoryDeleteDialog
event={selectedEvent} event={selectedEvent}
open={deleteOpen} open={deleteOpen}
setOpen={setDeleteOpen} setOpen={setDeleteOpen}
onConfirm={onDeleteConfirm} onConfirm={onDeleteConfirm}
/> />
<SignOnLogDeleteAllDialog <LoginHistoryDeleteAllDialog
open={deleteAllOpen} open={deleteAllOpen}
setOpen={setDeleteAllOpen} setOpen={setDeleteAllOpen}
onConfirm={onDeleteAllConfirm} onConfirm={onDeleteAllConfirm}

View File

@ -352,8 +352,8 @@ exports[`returns all baseRoutes 1`] = `
"menu": { "menu": {
"adminSettings": true, "adminSettings": true,
}, },
"path": "/admin/signons", "path": "/admin/logins",
"title": "Sign on log", "title": "Login history",
"type": "protected", "type": "protected",
}, },
{ {

View File

@ -43,7 +43,7 @@ import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView';
import { LazyAdmin } from 'component/admin/LazyAdmin'; import { LazyAdmin } from 'component/admin/LazyAdmin';
import { LazyProject } from 'component/project/Project/LazyProject'; import { LazyProject } from 'component/project/Project/LazyProject';
import { AdminRedirect } from 'component/admin/AdminRedirect'; import { AdminRedirect } from 'component/admin/AdminRedirect';
import { SignOnLog } from 'component/signOnLog/SignOnLog'; import { LoginHistory } from 'component/loginHistory/LoginHistory';
export const routes: IRoute[] = [ export const routes: IRoute[] = [
// Splash // Splash
@ -357,9 +357,9 @@ export const routes: IRoute[] = [
}, },
{ {
path: '/admin/signons', path: '/admin/logins',
title: 'Sign on log', title: 'Login history',
component: SignOnLog, component: LoginHistory,
type: 'protected', type: 'protected',
menu: { adminSettings: true }, menu: { adminSettings: true },
}, },
@ -448,10 +448,10 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
menu: { adminSettings: true }, menu: { adminSettings: true },
}, },
{ {
path: '/admin/signons', path: '/admin/logins',
title: 'Sign-on log', title: 'Login history',
menu: { adminSettings: true }, menu: { adminSettings: true },
flag: 'signOnLog', flag: 'loginHistory',
}, },
{ {
path: '/admin/users', path: '/admin/users',

View File

@ -1,6 +1,6 @@
import useAPI from '../useApi/useApi'; import useAPI from '../useApi/useApi';
export const useSignOnLogApi = () => { export const useLoginHistoryApi = () => {
const { loading, makeRequest, createRequest, errors } = useAPI({ const { loading, makeRequest, createRequest, errors } = useAPI({
propagateErrors: true, propagateErrors: true,
}); });
@ -8,7 +8,7 @@ export const useSignOnLogApi = () => {
const downloadCSV = async () => { const downloadCSV = async () => {
const requestId = 'downloadCSV'; const requestId = 'downloadCSV';
const req = createRequest( const req = createRequest(
'api/admin/signons', 'api/admin/logins',
{ {
method: 'GET', method: 'GET',
responseType: 'blob', responseType: 'blob',
@ -25,7 +25,7 @@ export const useSignOnLogApi = () => {
const removeEvent = async (eventId: number) => { const removeEvent = async (eventId: number) => {
const requestId = 'removeEvent'; const requestId = 'removeEvent';
const req = createRequest( const req = createRequest(
`api/admin/signons/${eventId}`, `api/admin/logins/${eventId}`,
{ method: 'DELETE' }, { method: 'DELETE' },
requestId requestId
); );
@ -36,7 +36,7 @@ export const useSignOnLogApi = () => {
const removeAllEvents = async () => { const removeAllEvents = async () => {
const requestId = 'removeAllEvents'; const requestId = 'removeAllEvents';
const req = createRequest( const req = createRequest(
'api/admin/signons', 'api/admin/logins',
{ method: 'DELETE' }, { method: 'DELETE' },
requestId requestId
); );

View File

@ -1,25 +1,25 @@
import { ISignOnEvent } from 'interfaces/signOnEvent'; import { ILoginEvent } from 'interfaces/loginEvent';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
import useUiConfig from '../useUiConfig/useUiConfig'; import useUiConfig from '../useUiConfig/useUiConfig';
export const useSignOnLog = () => { export const useLoginHistory = () => {
const { uiConfig, isEnterprise } = useUiConfig(); const { uiConfig, isEnterprise } = useUiConfig();
const { signOnLog } = uiConfig.flags; const { loginHistory } = uiConfig.flags;
const { data, error, mutate } = useConditionalSWR( const { data, error, mutate } = useConditionalSWR(
signOnLog && isEnterprise(), loginHistory && isEnterprise(),
{ events: [] }, { events: [] },
formatApiPath(`api/admin/signons`), formatApiPath(`api/admin/logins`),
fetcher fetcher
); );
return useMemo( return useMemo(
() => ({ () => ({
events: (data?.events ?? []) as ISignOnEvent[], events: (data?.events ?? []) as ILoginEvent[],
loading: !error && !data, loading: !error && !data,
refetch: () => mutate(), refetch: () => mutate(),
error, error,
@ -30,6 +30,6 @@ export const useSignOnLog = () => {
const fetcher = (path: string) => { const fetcher = (path: string) => {
return fetch(path) return fetch(path)
.then(handleErrorResponses('Sign-On Log')) .then(handleErrorResponses('Login History'))
.then(res => res.json()); .then(res => res.json());
}; };

View File

@ -1,4 +1,4 @@
export interface ISignOnEvent { export interface ILoginEvent {
id: number; id: number;
username: string; username: string;
auth_type: string; auth_type: string;

View File

@ -47,7 +47,7 @@ export interface IFlags {
showProjectApiAccess?: boolean; showProjectApiAccess?: boolean;
proPlanAutoCharge?: boolean; proPlanAutoCharge?: boolean;
notifications?: boolean; notifications?: boolean;
signOnLog?: boolean; loginHistory?: boolean;
} }
export interface IVersionInfo { export interface IVersionInfo {

View File

@ -74,6 +74,7 @@ exports[`should create default config 1`] = `
"embedProxy": true, "embedProxy": true,
"embedProxyFrontend": true, "embedProxyFrontend": true,
"featuresExportImport": false, "featuresExportImport": false,
"loginHistory": false,
"maintenanceMode": false, "maintenanceMode": false,
"messageBanner": false, "messageBanner": false,
"newProjectOverview": false, "newProjectOverview": false,
@ -83,7 +84,6 @@ exports[`should create default config 1`] = `
"proxyReturnAllToggles": false, "proxyReturnAllToggles": false,
"responseTimeWithAppNameKillSwitch": false, "responseTimeWithAppNameKillSwitch": false,
"showProjectApiAccess": false, "showProjectApiAccess": false,
"signOnLog": false,
"strictSchemaValidation": false, "strictSchemaValidation": false,
}, },
}, },
@ -96,6 +96,7 @@ exports[`should create default config 1`] = `
"embedProxy": true, "embedProxy": true,
"embedProxyFrontend": true, "embedProxyFrontend": true,
"featuresExportImport": false, "featuresExportImport": false,
"loginHistory": false,
"maintenanceMode": false, "maintenanceMode": false,
"messageBanner": false, "messageBanner": false,
"newProjectOverview": false, "newProjectOverview": false,
@ -105,7 +106,6 @@ exports[`should create default config 1`] = `
"proxyReturnAllToggles": false, "proxyReturnAllToggles": false,
"responseTimeWithAppNameKillSwitch": false, "responseTimeWithAppNameKillSwitch": false,
"showProjectApiAccess": false, "showProjectApiAccess": false,
"signOnLog": false,
"strictSchemaValidation": false, "strictSchemaValidation": false,
}, },
"externalResolver": { "externalResolver": {

View File

@ -63,7 +63,7 @@ const flags = {
false, false,
), ),
notifications: parseEnvVarBoolean(process.env.NOTIFICATIONS, false), notifications: parseEnvVarBoolean(process.env.NOTIFICATIONS, false),
signOnLog: parseEnvVarBoolean(process.env.UNLEASH_SIGN_ON_LOG, false), loginHistory: parseEnvVarBoolean(process.env.UNLEASH_LOGIN_HISTORY, false),
}; };
export const defaultExperimentalOptions: IExperimentalOptions = { export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -0,0 +1,20 @@
exports.up = function (db, cb) {
db.runSql(`ALTER TABLE sign_on_log RENAME TO login_history`, cb);
db.runSql(`DELETE FROM settings WHERE name = 'sign_on_log_retention'`, cb);
db.runSql(
`INSERT INTO settings(name, content) VALUES ('login_history_retention', '{"hours": 336}')`,
cb,
);
};
exports.down = function (db, cb) {
db.runSql(`ALTER TABLE login_history RENAME TO sign_on_log`, cb);
db.runSql(
`DELETE FROM settings WHERE name = 'login_history_retention'`,
cb,
);
db.runSql(
`INSERT INTO settings(name, content) VALUES ('sign_on_log_retention', '{"hours": 336}')`,
cb,
);
};