Refactor security logs configuration components and types

This commit is contained in:
ukrocks007 2024-04-09 16:36:43 +05:30
parent 508eec557b
commit 5882bb8309
12 changed files with 555 additions and 442 deletions

View File

@ -1,178 +1,28 @@
import { useEffect, useState } from 'react';
import useSWR from 'swr';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import type { SecurityLogsConfig } from '@boxyhq/saml-jackson';
import { fetcher } from '@lib/ui/utils';
import { errorToast, successToast } from '@components/Toaster';
import type { ApiError, ApiResponse, ApiSuccess } from 'types';
import { SinkConfigMapField, getFieldsFromSinkType } from '@lib/sinkConfigMap';
import { useRouter } from 'next/router';
import LicenseRequired from '@components/LicenseRequired';
import { ButtonDanger, ButtonPrimary, ConfirmationModal, LinkBack, Loading } from '@boxyhq/internal-ui';
import { SecurityLogsConfigEdit } from 'internal-ui/src/security-logs-config';
const UpdateApp = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
const { t } = useTranslation('common');
const UpdateConfig = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [config, setConfig] = useState<any>({});
const [fields, setFields] = useState<SinkConfigMapField[]>([]);
const { id } = router.query as { id: string };
const { data, error, isLoading } = useSWR<ApiSuccess<SecurityLogsConfig>, ApiError>(
`/api/admin/security-logs-config/${id}`,
fetcher,
{
revalidateOnFocus: false,
}
);
useEffect(() => {
if (data) {
setConfig(data.data?.config);
setFields(getFieldsFromSinkType(data.data?.type) || []);
}
}, [data]);
if (!hasValidLicense) {
return <LicenseRequired />;
}
if (error) {
errorToast(error.message);
return null;
}
if (isLoading) {
return <Loading />;
}
const onSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setLoading(true);
const rawResponse = await fetch(`/api/admin/security-logs-config/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
config,
}),
});
setLoading(false);
const response: ApiResponse<SecurityLogsConfig> = await rawResponse.json();
if ('error' in response) {
errorToast(response.error.message);
return;
}
if ('data' in response) {
successToast(t('security_logs_config_success'));
}
};
const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const target = event.target as HTMLInputElement;
setConfig({
...config,
[target.id]: target.value,
});
const urls = {
getById: (id: string) => `/api/admin/security-logs-config/${id}`,
updateById: (id: string) => `/api/admin/security-logs-config/${id}`,
deleteById: (id: string) => `/api/admin/security-logs-config/${id}`,
listConfigs: '/admin/settings/security-logs',
};
return (
<>
<LinkBack href='/admin/settings/security-logs' />
<div className='mb-5 flex items-center justify-between'>
<h2 className='mt-5 font-bold text-gray-700 md:text-xl'>{t('security_logs_config_update')}</h2>
</div>
<div className='rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
<form onSubmit={onSubmit}>
<div className='space-y-3'>
{fields.map((field) => {
return (
<div className='form-control w-full md:w-1/2' key={field.index}>
<label className='label'>
<span className='label-text'>{t(field.label)}</span>
</label>
<input
type={field.type}
className='input-bordered input'
id={field.name}
placeholder={t(field.placeholder)}
value={config[field.name]}
onChange={onChange}
/>
</div>
);
})}
<div>
<ButtonPrimary type='submit' loading={loading}>
{t('bui-shared-save-changes')}
</ButtonPrimary>
</div>
</div>
</form>
</div>
<DeleteApp id={id} />
<SecurityLogsConfigEdit id={id} urls={urls} onSuccess={successToast} onError={errorToast} />
</>
);
};
export const DeleteApp = ({ id }: { id }) => {
const { t } = useTranslation('common');
const [delModalVisible, setDelModalVisible] = useState(false);
const deleteApp = async () => {
const rawResponse = await fetch(`/api/admin/security-logs-config/${id}`, {
method: 'DELETE',
});
const response: ApiResponse<unknown> = await rawResponse.json();
if ('error' in response) {
errorToast(response.error.message);
return;
}
if ('data' in response) {
successToast(t('security_logs_config_delete_success'));
window.location.href = '/admin/settings/security-logs';
}
};
return (
<>
<section className='mt-5 flex items-center rounded bg-red-100 p-6 text-red-900'>
<div className='flex-1'>
<h6 className='mb-1 font-medium'>{t('delete_this_security_logs_config')}</h6>
<p className='font-light'>{t('security_logs_wont_be_sent_to_this')}</p>
</div>
<ButtonDanger
type='button'
data-modal-toggle='popup-modal'
onClick={() => {
setDelModalVisible(true);
}}>
{t('bui-shared-delete')}
</ButtonDanger>
</section>
<ConfirmationModal
title={t('delete_the_security_logs_config')}
description={t('confirmation_modal_description_config')}
visible={delModalVisible}
onConfirm={deleteApp}
onCancel={() => {
setDelModalVisible(false);
}}
/>
</>
);
};
export default UpdateApp;
export default UpdateConfig;

View File

@ -1,153 +1,17 @@
import { useEffect } from 'react';
import type { SecurityLogsConfig } from '@boxyhq/saml-jackson';
import useSWR from 'swr';
import { useTranslation } from 'next-i18next';
import type { ApiError, ApiSuccess } from 'types';
import { fetcher } from '@lib/ui/utils';
import { EmptyState, LinkPrimary, Loading, pageLimit, Pagination, Table } from '@boxyhq/internal-ui';
import usePaginate from '@lib/ui/hooks/usePaginate';
import PencilIcon from '@heroicons/react/24/outline/PencilIcon';
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
import router from 'next/router';
import LicenseRequired from '@components/LicenseRequired';
import { errorToast } from '@components/Toaster';
import { getDisplayTypeFromSinkType } from '@lib/sinkConfigMap';
import { TableBodyType } from 'internal-ui/src/shared/Table';
import { SecurityLogsConfigs } from 'internal-ui/src/security-logs-config';
const ConfigList = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
const { t } = useTranslation('common');
const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate();
let getAppsUrl = `/api/admin/security-logs-config?offset=${paginate.offset}&limit=${pageLimit}`;
// Use the (next)pageToken mapped to the previous page offset to get the current page
if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) {
getAppsUrl += `&pageToken=${pageTokenMap[paginate.offset - pageLimit]}`;
}
const { data, error, isLoading } = useSWR<ApiSuccess<SecurityLogsConfig[]>, ApiError>(getAppsUrl, fetcher);
const nextPageToken = data?.pageToken;
// store the nextPageToken against the pageOffset
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (!hasValidLicense) {
return <LicenseRequired />;
}
if (isLoading) {
return <Loading />;
}
if (error) {
errorToast(error.message);
return;
}
const configs = data?.data || [];
const noConfigs = configs.length === 0 && paginate.offset === 0;
const noMoreResults = configs.length === 0 && paginate.offset > 0;
const columns = [
{
key: 'name',
label: t('bui-shared-name'),
wrap: true,
dataIndex: 'name',
},
{
key: 'type',
label: t('bui-shared-type'),
wrap: true,
dataIndex: 'type',
},
{
key: 'tenant',
label: t('bui-shared-tenant'),
wrap: true,
dataIndex: 'tenant',
},
{
key: 'actions',
label: t('bui-shared-actions'),
wrap: true,
dataIndex: null,
},
];
const cols = columns.map(({ label }) => label);
const body: TableBodyType[] = configs.map((config) => {
return {
id: config.id,
cells: columns.map((column) => {
const dataIndex = column.dataIndex as string;
if (dataIndex === null) {
return {
actions: [
{
text: t('bui-shared-edit'),
onClick: () => router.push(`/admin/settings/security-logs/${config.id}/edit`),
icon: <PencilIcon className='w-5' />,
},
],
};
} else if (dataIndex === 'type') {
return {
wrap: column.wrap,
text: getDisplayTypeFromSinkType(config.type),
};
} else {
return {
wrap: column.wrap,
text: config[dataIndex],
};
}
}),
};
});
return (
<>
<div className='mb-5 flex items-center justify-between'>
<h2 className='font-bold text-gray-700 dark:text-white md:text-xl'>{t('security_logs_configs')}</h2>
<div className='flex'>
<LinkPrimary className='m-2' Icon={PlusIcon} href='/admin/settings/security-logs/new'>
{t('new_security_logs_config')}
</LinkPrimary>
</div>
</div>
{noConfigs ? (
<>
<EmptyState title={t('no_security_logs_config')} />
</>
) : (
<>
<Table noMoreResults={noMoreResults} cols={cols} body={body} />
<Pagination
itemsCount={configs.length}
offset={paginate.offset}
onPrevClick={() => {
setPaginate({
offset: paginate.offset - pageLimit,
});
}}
onNextClick={() => {
setPaginate({
offset: paginate.offset + pageLimit,
});
}}
/>
</>
)}
</>
);
const urls = {
getConfigs: '/api/admin/security-logs-config',
createConfig: '/admin/settings/security-logs/new',
editLink: (id) => `/admin/settings/security-logs/${id}`,
};
return <SecurityLogsConfigs urls={urls} />;
};
export default ConfigList;

View File

@ -1,132 +1,21 @@
import { useState } from 'react';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { errorToast, successToast } from '@components/Toaster';
import { successToast } from '@components/Toaster';
import LicenseRequired from '@components/LicenseRequired';
import { configMap } from '@lib/sinkConfigMap';
import { ButtonPrimary, LinkBack } from '@boxyhq/internal-ui';
import { SecurityLogsConfigCreate } from 'internal-ui/src/security-logs-config';
const NewConfiguration = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
const { t } = useTranslation('common');
const router = useRouter();
const [loading, setLoading] = useState(false);
const [config, setConfig] = useState({});
const [name, setName] = useState('');
const [type, setType] = useState(t('select_type'));
if (!hasValidLicense) {
return <LicenseRequired />;
}
const onSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setLoading(true);
const rawResponse = await fetch('/api/admin/security-logs-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, type: configMap[type].type, config }),
});
setLoading(false);
const response = await rawResponse.json();
if ('error' in response) {
errorToast(response.error.message);
return;
}
if ('data' in response) {
successToast(t('security_logs_config_new_success'));
router.replace(`/admin/settings/security-logs`);
}
const urls = {
createConfig: '/api/admin/security-logs-config',
listConfigs: '/admin/settings/security-logs',
};
const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const target = event.target as HTMLInputElement;
setConfig({
...config,
[target.id]: target.value,
});
};
return (
<>
<LinkBack href='/admin/settings/security-logs' />
<h2 className='mb-5 mt-5 font-bold text-gray-700 md:text-xl'>{t('security_logs_config_add_new')}</h2>
<div className='rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
<form onSubmit={onSubmit}>
<div className='flex flex-col space-y-3'>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('bui-shared-type')}</span>
</label>
<select
className='select-bordered select w-full'
id='type'
value={type}
onChange={(e) => {
setType(e.target.value);
}}
required>
{[t('select_type'), ...Object.keys(configMap)].map((key) => {
return (
<option key={key} value={key}>
{key}
</option>
);
})}
</select>
</div>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('bui-shared-name')}</span>
</label>
<input
type='text'
id='name'
className='input-bordered input'
value={name}
required={false}
onChange={(e) => setName(e.target.value)}
placeholder={t('bui-shared-name')}
/>
</div>
{type && (
<>
{configMap[type] &&
configMap[type].fields.map((field) => {
return (
<div key={field.index} className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t(field.label)}</span>
</label>
<input
type={field.type}
id={field.name}
className='input-bordered input'
required
onChange={onChange}
placeholder={t(field.placeholder)}
/>
</div>
);
})}
</>
)}
<div>
<ButtonPrimary loading={loading}>{t('create_config')}</ButtonPrimary>
</div>
</div>
</form>
</div>
</>
);
return <SecurityLogsConfigCreate urls={urls} onSuccess={successToast} />;
};
export default NewConfiguration;

View File

@ -0,0 +1,135 @@
import { useState } from 'react';
import { useTranslation } from 'next-i18next';
import { ButtonPrimary, LinkBack } from '@boxyhq/internal-ui';
import { useRouter } from '../hooks';
import { configMap } from './lib';
import { Error } from '../shared';
export const SecurityLogsConfigCreate = ({
urls,
onSuccess,
}: {
urls: {
createConfig: string;
listConfigs: string;
};
onSuccess: (message: string) => void;
}) => {
const { t } = useTranslation('common');
const { router } = useRouter();
const [loading, setLoading] = useState(false);
const [config, setConfig] = useState({});
const [name, setName] = useState('');
const [type, setType] = useState(t('bui-shared-select-type'));
const onSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setLoading(true);
const rawResponse = await fetch(urls.createConfig, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, type: configMap[type].type, config }),
});
setLoading(false);
const response = await rawResponse.json();
if ('error' in response) {
return <Error message={response.error.message} />;
}
if ('data' in response) {
onSuccess(t('bui-slc-new-success'));
router?.replace(urls.listConfigs);
}
};
const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const target = event.target as HTMLInputElement;
setConfig({
...config,
[target.id]: target.value,
});
};
return (
<>
<LinkBack href={urls.listConfigs} />
<h2 className='mb-5 mt-5 font-bold text-gray-700 md:text-xl'>{t('bui-slc-add')}</h2>
<div className='rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
<form onSubmit={onSubmit}>
<div className='flex flex-col space-y-3'>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('bui-shared-type')}</span>
</label>
<select
className='select-bordered select w-full'
id='type'
value={type}
onChange={(e) => {
setType(e.target.value);
}}
required>
{[t('bui-shared-select-type'), ...Object.keys(configMap)].map((key) => {
return (
<option key={key} value={key}>
{key}
</option>
);
})}
</select>
</div>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('bui-shared-name')}</span>
</label>
<input
type='text'
id='name'
className='input-bordered input'
value={name}
required={false}
onChange={(e) => setName(e.target.value)}
placeholder={t('bui-shared-name')}
/>
</div>
{type && (
<>
{configMap[type] &&
configMap[type].fields.map((field) => {
return (
<div key={field.index} className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t(field.label)}</span>
</label>
<input
type={field.type}
id={field.name}
className='input-bordered input'
required
onChange={onChange}
placeholder={t(field.placeholder)}
/>
</div>
);
})}
</>
)}
<div>
<ButtonPrimary loading={loading}>{t('bui-slc-create-config')}</ButtonPrimary>
</div>
</div>
</form>
</div>
</>
);
};

View File

@ -0,0 +1,69 @@
import { useState } from 'react';
import { useTranslation } from 'next-i18next';
import { ButtonDanger, ConfirmationModal } from '../shared';
import { useRouter } from '../hooks';
import { ApiResponse } from '../types';
export const SecurityLogsConfigDelete = ({
id,
urls,
onError,
onSuccess,
}: {
id: string;
urls: { deleteById: (id: string) => string; listConfigs: string };
onError: (string) => void;
onSuccess: (string) => void;
}) => {
const { t } = useTranslation('common');
const { router } = useRouter();
const [delModalVisible, setDelModalVisible] = useState(false);
const deleteApp = async () => {
const rawResponse = await fetch(urls.deleteById(id), {
method: 'DELETE',
});
const response: ApiResponse<unknown> = await rawResponse.json();
if ('error' in response) {
onError(response.error.message);
return;
}
if ('data' in response) {
onSuccess(t('bui-slc-delete-success'));
router?.replace(urls.listConfigs);
}
};
return (
<>
<section className='mt-5 flex items-center rounded bg-red-100 p-6 text-red-900'>
<div className='flex-1'>
<h6 className='mb-1 font-medium'>{t('bui-slc-delete-confirmation')}</h6>
<p className='font-light'>{t('bui-slc-logs-noop')}</p>
</div>
<ButtonDanger
type='button'
data-modal-toggle='popup-modal'
onClick={() => {
setDelModalVisible(true);
}}>
{t('bui-shared-delete')}
</ButtonDanger>
</section>
<ConfirmationModal
title={t('bui-slc-delete')}
description={t('bui-slc-delete-modal-confirmation')}
visible={delModalVisible}
onConfirm={deleteApp}
onCancel={() => {
setDelModalVisible(false);
}}
/>
</>
);
};

View File

@ -0,0 +1,137 @@
import { useEffect, useState } from 'react';
import useSWR from 'swr';
import { useTranslation } from 'next-i18next';
import type { SecurityLogsConfig } from '@boxyhq/saml-jackson';
import { fetcher } from '../utils';
import { ButtonPrimary, LinkBack, Loading } from '../shared';
import { SinkConfigMapField, getFieldsFromSinkType } from './lib';
import { Error } from '../shared';
import { ApiError, ApiResponse, ApiSuccess } from '../types';
import { useRouter } from '../hooks';
import { SecurityLogsConfigDelete } from './SecurityLogsConfigDelete';
export const SecurityLogsConfigEdit = ({
id,
urls,
onError,
onSuccess,
}: {
id: string;
urls: {
getById: (id: string) => string;
updateById: (id: string) => string;
deleteById: (id: string) => string;
listConfigs: string;
};
onError: (error: string) => void;
onSuccess: (message: string) => void;
}) => {
const { t } = useTranslation('common');
const { router } = useRouter();
const [loading, setLoading] = useState(false);
const [config, setConfig] = useState<any>({});
const [fields, setFields] = useState<SinkConfigMapField[]>([]);
const { data, error, isLoading } = useSWR<ApiSuccess<SecurityLogsConfig>, ApiError>(
urls.getById(id),
fetcher,
{
revalidateOnFocus: false,
}
);
useEffect(() => {
if (data) {
setConfig(data.data?.config);
setFields(getFieldsFromSinkType(data.data?.type) || []);
}
}, [data]);
if (error) {
<Error message={error.message} />;
return null;
}
if (isLoading) {
return <Loading />;
}
const onSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setLoading(true);
const rawResponse = await fetch(urls.updateById(id), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
config,
}),
});
setLoading(false);
const response: ApiResponse<SecurityLogsConfig> = await rawResponse.json();
if ('error' in response) {
onError(response.error.message);
return;
}
if ('data' in response) {
onSuccess(t('bui-slc-update-success'));
router?.replace(urls.listConfigs);
}
};
const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const target = event.target as HTMLInputElement;
setConfig({
...config,
[target.id]: target.value,
});
};
return (
<>
<LinkBack href={urls.listConfigs} />
<div className='mb-5 flex items-center justify-between'>
<h2 className='mt-5 font-bold text-gray-700 md:text-xl'>{t('bui-slc-update')}</h2>
</div>
<div className='rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
<form onSubmit={onSubmit}>
<div className='space-y-3'>
{fields.map((field) => {
return (
<div className='form-control w-full md:w-1/2' key={field.index}>
<label className='label'>
<span className='label-text'>{t(field.label)}</span>
</label>
<input
type={field.type}
className='input-bordered input'
id={field.name}
placeholder={t(field.placeholder)}
value={config[field.name]}
onChange={onChange}
/>
</div>
);
})}
<div>
<ButtonPrimary type='submit' loading={loading}>
{t('bui-shared-save-changes')}
</ButtonPrimary>
</div>
</div>
</form>
</div>
<SecurityLogsConfigDelete id={id} urls={urls} onSuccess={onSuccess} onError={onError} />
</>
);
};

View File

@ -0,0 +1,163 @@
import { useEffect } from 'react';
import type { SecurityLogsConfig } from '@boxyhq/saml-jackson';
import useSWR from 'swr';
import { EmptyState, LinkPrimary, Loading, pageLimit, Pagination, Table } from '@boxyhq/internal-ui';
import { useTranslation } from 'next-i18next';
import PencilIcon from '@heroicons/react/24/outline/PencilIcon';
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
import { addQueryParamsToPath, fetcher } from '../utils';
import { usePaginate, useRouter } from '../hooks';
import { TableBodyType } from '../shared/Table';
import { ApiError, ApiSuccess } from '../types';
import { getDisplayTypeFromSinkType } from './lib';
import { Error } from '../shared';
export const SecurityLogsConfigs = ({
urls,
}: {
urls: {
getConfigs: string;
createConfig: string;
editLink: (id: string) => string;
};
}) => {
const { t } = useTranslation('common');
const { router } = useRouter();
const { paginate, setPaginate, pageTokenMap, setPageTokenMap } = usePaginate(router!);
const params = {
pageOffset: paginate.offset,
pageLimit: pageLimit,
};
// For DynamoDB
if (paginate.offset > 0 && pageTokenMap[paginate.offset - pageLimit]) {
params['pageToken'] = pageTokenMap[paginate.offset - pageLimit];
}
const getConfigsUrl = addQueryParamsToPath(urls.getConfigs, params);
const { data, error, isLoading } = useSWR<ApiSuccess<SecurityLogsConfig[]>, ApiError>(
getConfigsUrl,
fetcher
);
const nextPageToken = data?.pageToken;
// store the nextPageToken against the pageOffset
useEffect(() => {
if (nextPageToken) {
setPageTokenMap((tokenMap) => ({ ...tokenMap, [paginate.offset]: nextPageToken }));
}
}, [nextPageToken, paginate.offset]);
if (isLoading) {
return <Loading />;
}
if (error) {
return <Error message={error.message} />;
}
const configs = data?.data || [];
const noConfigs = configs.length === 0 && paginate.offset === 0;
const noMoreResults = configs.length === 0 && paginate.offset > 0;
const columns = [
{
key: 'name',
label: t('bui-shared-name'),
wrap: true,
dataIndex: 'name',
},
{
key: 'type',
label: t('bui-shared-type'),
wrap: true,
dataIndex: 'type',
},
{
key: 'tenant',
label: t('bui-shared-tenant'),
wrap: true,
dataIndex: 'tenant',
},
{
key: 'actions',
label: t('bui-shared-actions'),
wrap: true,
dataIndex: null,
},
];
const cols = columns.map(({ label }) => label);
const body: TableBodyType[] = configs.map((config) => {
return {
id: config.id,
cells: columns.map((column) => {
const dataIndex = column.dataIndex as string;
if (dataIndex === null) {
return {
actions: [
{
text: t('bui-shared-edit'),
onClick: () => router?.replace(urls.editLink(config.id)),
icon: <PencilIcon className='w-5' />,
},
],
};
} else if (dataIndex === 'type') {
return {
wrap: column.wrap,
text: getDisplayTypeFromSinkType(config.type),
};
} else {
return {
wrap: column.wrap,
text: config[dataIndex],
};
}
}),
};
});
return (
<>
<div className='mb-5 flex items-center justify-between'>
<h2 className='font-bold text-gray-700 dark:text-white md:text-xl'>{t('bui-slc')}</h2>
<div className='flex'>
<LinkPrimary className='m-2' Icon={PlusIcon} href={urls.createConfig}>
{t('bui-slc-new')}
</LinkPrimary>
</div>
</div>
{noConfigs ? (
<>
<EmptyState title={t('bui-slc-empty')} />
</>
) : (
<>
<Table noMoreResults={noMoreResults} cols={cols} body={body} />
<Pagination
itemsCount={configs.length}
offset={paginate.offset}
onPrevClick={() => {
setPaginate({
offset: paginate.offset - pageLimit,
});
}}
onNextClick={() => {
setPaginate({
offset: paginate.offset + pageLimit,
});
}}
/>
</>
)}
</>
);
};

View File

@ -0,0 +1,4 @@
export { SecurityLogsConfigs } from './SecurityLogsConfigs';
export { SecurityLogsConfigCreate } from './SecurityLogsConfigCreate';
export { SecurityLogsConfigEdit } from './SecurityLogsConfigEdit';
export { configMap, getDisplayTypeFromSinkType } from './lib';

View File

@ -19,17 +19,17 @@ export const configMap = {
fields: [
{
index: 1,
label: 'splunk_event_collector_url',
label: 'bui-splunk-collector-url',
name: 'endpoint',
type: 'string',
placeholder: 'splunk_hec_endpoint_placeholder',
placeholder: 'bui-splunk-hec-endpoint-placeholder',
},
{
index: 2,
label: 'default_token',
label: 'bui-default-token',
name: 'default_token',
type: 'string',
placeholder: 'default_token_placeholder',
placeholder: 'bui-default-token-placeholder',
},
],
},

View File

@ -5,6 +5,8 @@ export interface ApiError extends Error {
status: number;
}
export type ApiResponse<T = any> = ApiSuccess<T> | { error: ApiError };
enum DirectorySyncProviders {
'azure-scim-v2' = 'Azure SCIM v2.0',
'onelogin-scim-v2' = 'OneLogin SCIM v2.0',

View File

@ -108,24 +108,6 @@
"choose_an_identity_provider_to_continue": "Choose an Identity Provider to continue. If you don't see your Identity Provider, please contact your administrator.",
"choose_an_app_to_continue": "Choose an app to continue. If you don't see your app, please contact your administrator.",
"no_saml_response_try_again": "No SAMLResponse found. Please try again.",
"security_logs_config_new_success": "Security logs config created successfully.",
"security_logs_wont_be_sent_to_this": "Security logs won't be sent to this destination.",
"confirmation_modal_description_config": "This action cannot be undone. This will permanently delete the Configuration.",
"security_logs_configs": "Security Logs Configurations",
"no_security_logs_config": "No Security Logs Configuration found.",
"splunk_event_collector_url": "Splunk HTTP Event Collector Url",
"splunk_hec_endpoint_placeholder": "https://splunk.example.com:8088",
"default_token": "Default Token",
"default_token_placeholder": "Token generated by splunk for HEC",
"security_logs_config_add_new": "Add Security Logs Configuration",
"create_config": "Create Configuration",
"security_logs_config_success": "Security logs configuration updated successfully.",
"security_logs_config_update": "Update Security Logs Configuration",
"security_logs_config_delete_success": "Security Logs Configuration deleted successfully",
"delete_this_security_logs_config": "Delete this Security Logs Configuration",
"delete_the_security_logs_config": "Delete the Security logs config?",
"new_security_logs_config": "New Configuration",
"select_type": "Select a type",
"sso_connection_created_successfully": "SSO Connection created successfully",
"sso_connection_deleted_successfully": "SSO Connection deleted successfully",
"saml_federation_entity_id_generated": "SP Entity ID generated",
@ -133,7 +115,10 @@
"setup-link-regenerated": "The setup link regenerated.",
"setup-link-copied": "The setup link copied to the clipboard.",
"setup-link-deleted": "The setup link deleted.",
"bui-default-token": "Default Token",
"bui-default-token-placeholder": "Token generated by splunk for HEC",
"bui-shared-name": "Name",
"bui-shared-select-type": "Select a type",
"bui-shared-type": "Type",
"bui-shared-tenant": "Tenant",
"bui-shared-product": "Product",
@ -163,6 +148,21 @@
"bui-shared-favicon-url-desc": "Provide a URL to your favicon. Recommend PNG, SVG, or ICO formats.",
"bui-shared-primary-color": "Primary Color",
"bui-shared-primary-color-desc": "Primary color will be applied to buttons, links, and other elements.",
"bui-slc": "Security Logs Configurations",
"bui-slc-add": "Add Security Logs Configuration",
"bui-slc-create-config": "Create Configuration",
"bui-slc-delete": "Delete the Security logs config?",
"bui-slc-delete-confirmation": "Delete this Security Logs Configuration",
"bui-slc-delete-modal-confirmation": "This action cannot be undone. This will permanently delete the Configuration.",
"bui-slc-delete-success": "Security Logs Configuration deleted successfully",
"bui-slc-empty": "No Security Logs Configuration found.",
"bui-slc-logs-noop": "Security logs won't be sent to this destination.",
"bui-slc-new": "New Configuration",
"bui-slc-new-success": "Security logs config created successfully.",
"bui-slc-update": "Update Security Logs Configuration",
"bui-slc-update-success": "Security logs configuration updated successfully.",
"bui-splunk-collector-url": "Splunk HTTP Event Collector Url",
"bui-splunk-hec-endpoint-placeholder": "https://splunk.example.com:8088",
"bui-wku-heading": "Here are the set of URIs you would need access to:",
"bui-wku-idp-configuration-links": "Identity Provider Configuration links",
"bui-wku-desc-idp-configuration": "Links for SAML/OIDC IdP setup",