mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-23 16:59:08 +00:00
feat: create certificate page
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Button,
|
||||||
|
} from '@heroui/react';
|
||||||
|
import type { CertificateDTO } from '../../duck';
|
||||||
|
|
||||||
|
export interface CertificateDetailProps {
|
||||||
|
cetificate: CertificateDTO;
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CertificateDetail: FC<CertificateDetailProps> = (props) => {
|
||||||
|
const { cetificate, isOpen, onOpenChange } = props;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent>
|
||||||
|
{(onClose) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
|
Modal Title
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div>
|
||||||
|
<h4>Basic Information</h4>
|
||||||
|
<p>
|
||||||
|
<strong>Name:</strong> ${cetificate.name}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Domain:</strong> ${cetificate.domain}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Type:</strong> ${cetificate.cert_type}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Auto Renew:</strong>
|
||||||
|
{cetificate.auto_renew ? 'Yes' : 'No'}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Created:</strong>
|
||||||
|
{new Date(cetificate.created_at).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Expires:</strong>
|
||||||
|
{new Date(cetificate.expires_at).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Certificate PEM</h4>
|
||||||
|
<div className="cert-details">
|
||||||
|
{cetificate.certificate_pem || 'Not available'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Private Key</h4>
|
||||||
|
<div className="cert-details">
|
||||||
|
{cetificate.has_private_key
|
||||||
|
? '[Hidden for security]'
|
||||||
|
: 'Not available'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="danger" variant="light" onPress={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@@ -0,0 +1,93 @@
|
|||||||
|
import { useEffect, type FC } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Button,
|
||||||
|
} from '@heroui/react';
|
||||||
|
import { useAppDispatch } from '../../../../common/hooks';
|
||||||
|
import { getCertificate } from '../../duck/api';
|
||||||
|
import { updateCertificate, type EditCertificateDTO } from '../../duck';
|
||||||
|
|
||||||
|
export interface CertificateEditProps {
|
||||||
|
certificateId: string;
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CertificateEdit: FC<CertificateEditProps> = (props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { certificateId, isOpen, onOpenChange } = props;
|
||||||
|
const { register, handleSubmit, reset } = useForm<EditCertificateDTO>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getCertificate(certificateId).then((response) => {
|
||||||
|
const { data } = response;
|
||||||
|
reset({
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [certificateId]);
|
||||||
|
|
||||||
|
const onSubmit = (values: EditCertificateDTO) => {
|
||||||
|
dispatch(
|
||||||
|
updateCertificate({
|
||||||
|
id: certificateId,
|
||||||
|
certificate: values
|
||||||
|
}),
|
||||||
|
).then(() => {
|
||||||
|
onOpenChange();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<ModalContent>
|
||||||
|
{(onClose) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
|
Modal Title
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Name:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register('name', { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Domain:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register('domain', { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" {...register('auto_renew')} /> Auto
|
||||||
|
Renew
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button color="danger" variant="light" onPress={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@@ -0,0 +1,24 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import type { CertificateDTO } from '../../duck';
|
||||||
|
import { CertificateView } from './certificate-view';
|
||||||
|
|
||||||
|
export interface CertificateList {
|
||||||
|
certificates: CertificateDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CertificateList: FC<CertificateList> = ({ certificates }) => {
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Expires</th>
|
||||||
|
<th>Auto Renew</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
{certificates
|
||||||
|
.map((certificate)=><CertificateView certificate={certificate} key={certificate.id}/>)}
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
};
|
@@ -0,0 +1,56 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import { deleteCertificateAction, type CertificateDTO } from '../../duck';
|
||||||
|
import { useDisclosure } from '@heroui/react';
|
||||||
|
import { CertificateDetail } from './certificate-details';
|
||||||
|
import { CertificateEdit } from './certificate-edit';
|
||||||
|
import { useAppDispatch } from '../../../../common/hooks';
|
||||||
|
|
||||||
|
export interface CertificateViewProps {
|
||||||
|
certificate: CertificateDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CertificateView: FC<CertificateViewProps> = ({ certificate }) => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const detailDisclosure = useDisclosure();
|
||||||
|
const editDisclosure = useDisclosure();
|
||||||
|
|
||||||
|
const handleDeleteCertificate = () => {
|
||||||
|
if (confirm('Delete certificate?')) {
|
||||||
|
dispatch(deleteCertificateAction(certificate.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr>
|
||||||
|
<td>{certificate.name}</td>
|
||||||
|
<td>{certificate.domain}</td>
|
||||||
|
<td>{certificate.cert_type}</td>
|
||||||
|
<td>{new Date(certificate.expires_at).toLocaleDateString()}</td>
|
||||||
|
<td>{certificate.auto_renew ? 'Yes' : 'No'}</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={detailDisclosure.onOpenChange}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={editDisclosure.onOpenChange}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={handleDeleteCertificate}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<CertificateDetail cetificate={certificate} {...detailDisclosure} />
|
||||||
|
<CertificateEdit {...editDisclosure} certificateId={certificate.id} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@@ -0,0 +1 @@
|
|||||||
|
export * from './certificate-list'
|
@@ -0,0 +1,51 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { createCertificateAction, type CreateCertificateDTO } from '../../duck';
|
||||||
|
import { useAppDispatch } from '../../../../common/hooks';
|
||||||
|
|
||||||
|
export const CreateCertificate: FC = () => {
|
||||||
|
const { handleSubmit, register, reset } = useForm<CreateCertificateDTO>();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const onSubmit = (values: CreateCertificateDTO) => {
|
||||||
|
dispatch(createCertificateAction(values)).then(() => {
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form id="certificateForm" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Name:</label>
|
||||||
|
<input type="text" {...register('name', { required: true })} />
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Domain:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="example.com"
|
||||||
|
{...register('domain', { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Certificate Type:</label>
|
||||||
|
<select id="certType" {...register('cert_type', { required: true })}>
|
||||||
|
<option value="self_signed">Self-Signed</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="certAutoRenew"
|
||||||
|
{...register('auto_renew')}
|
||||||
|
/>{' '}
|
||||||
|
Auto Renew
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Generate Certificate
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
@@ -0,0 +1 @@
|
|||||||
|
export * from './create-certificate'
|
1
client/src/features/certificates/components/index.ts
Normal file
1
client/src/features/certificates/components/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './create-certificate'
|
@@ -1,10 +1,12 @@
|
|||||||
import { certificateSlice } from './slice';
|
import { certificateSlice } from './slice';
|
||||||
import { getCertificates } from './api';
|
import { createCertificate, deleteCertificate, getCertificates, patchCertificate } from './api';
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import { getCertificatesState } from './selectors';
|
import { getCertificatesState } from './selectors';
|
||||||
import type { RootState } from '../../../store';
|
import type { RootState } from '../../../store';
|
||||||
|
import { appNotificator } from '../../../utils/notification/app-notificator';
|
||||||
|
import type { CreateCertificateDTO, EditCertificateDTO } from './dto';
|
||||||
|
|
||||||
const PREFFIX = 'certificates'
|
const PREFFIX = 'certificates';
|
||||||
|
|
||||||
export const fetchCertificates = createAsyncThunk(
|
export const fetchCertificates = createAsyncThunk(
|
||||||
`${PREFFIX}/fetchAll`,
|
`${PREFFIX}/fetchAll`,
|
||||||
@@ -18,7 +20,6 @@ export const fetchCertificates = createAsyncThunk(
|
|||||||
dispatch(certificateSlice.actions.setLoading(true));
|
dispatch(certificateSlice.actions.setLoading(true));
|
||||||
const response = await getCertificates().then(({ data }) => data);
|
const response = await getCertificates().then(({ data }) => data);
|
||||||
dispatch(certificateSlice.actions.setUsers(response));
|
dispatch(certificateSlice.actions.setUsers(response));
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message =
|
const message =
|
||||||
e instanceof Error ? e.message : `Unknown error in ${PREFFIX}/fetchAll`;
|
e instanceof Error ? e.message : `Unknown error in ${PREFFIX}/fetchAll`;
|
||||||
@@ -28,3 +29,71 @@ export const fetchCertificates = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const createCertificateAction = createAsyncThunk(
|
||||||
|
`${PREFFIX}/createCertificates`,
|
||||||
|
async (params: CreateCertificateDTO, { dispatch }) => {
|
||||||
|
try {
|
||||||
|
await createCertificate(params);
|
||||||
|
dispatch(fetchCertificates());
|
||||||
|
} catch (e) {
|
||||||
|
appNotificator.add({
|
||||||
|
message:
|
||||||
|
e instanceof Error
|
||||||
|
? e.message
|
||||||
|
: `Unknown error in ${PREFFIX}/createCertificates`,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateCertificate = createAsyncThunk(
|
||||||
|
`${PREFFIX}/updateCertificate`,
|
||||||
|
async (
|
||||||
|
params: {
|
||||||
|
id: string;
|
||||||
|
certificate: EditCertificateDTO;
|
||||||
|
},
|
||||||
|
{ dispatch },
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await patchCertificate(params.id, params.certificate);
|
||||||
|
dispatch(fetchCertificates());
|
||||||
|
appNotificator.add({
|
||||||
|
message: 'Template updated',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
appNotificator.add({
|
||||||
|
type: 'error',
|
||||||
|
message:
|
||||||
|
e instanceof Error
|
||||||
|
? `Error updating: ${e.message}`
|
||||||
|
: `Unknown error in ${PREFFIX}/updateTemplate`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteCertificateAction = createAsyncThunk(
|
||||||
|
`${PREFFIX}/deleteCertificate`,
|
||||||
|
async (id: string, { dispatch }) => {
|
||||||
|
try {
|
||||||
|
await deleteCertificate(id);
|
||||||
|
appNotificator.add({
|
||||||
|
message: 'Certificate deleted',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
dispatch(fetchCertificates());
|
||||||
|
} catch (e) {
|
||||||
|
appNotificator.add({
|
||||||
|
type: 'error',
|
||||||
|
message:
|
||||||
|
e instanceof Error
|
||||||
|
? `Delete error: ${e.message}`
|
||||||
|
: `Unknown error in ${PREFFIX}/deleteCertificate`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
@@ -1,6 +1,20 @@
|
|||||||
import type { AxiosResponse } from 'axios';
|
import type { AxiosResponse } from 'axios';
|
||||||
import { api } from '../../../api/api';
|
import { api } from '../../../api/api';
|
||||||
import type { CertificateDTO } from './dto';
|
import type { CertificateDTO, CreateCertificateDTO, EditCertificateDTO } from './dto';
|
||||||
|
|
||||||
export const getCertificates = () =>
|
export const getCertificates = () =>
|
||||||
api.get<never, AxiosResponse<CertificateDTO[]>>('/certificates');
|
api.get<never, AxiosResponse<CertificateDTO[]>>('/certificates');
|
||||||
|
|
||||||
|
export const createCertificate = (params: CreateCertificateDTO) =>
|
||||||
|
api.post<AxiosResponse>('/certificates', params, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getCertificate = (id: string) => api.get<never, AxiosResponse<CertificateDTO>>(`/certificates/${id}`)
|
||||||
|
|
||||||
|
export const patchCertificate = (id: string, certificate: EditCertificateDTO) =>
|
||||||
|
api.put(`/certificates/${id}`, certificate, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteCertificate = (id: string) => api.delete(`/certificates/${id}`);
|
||||||
|
@@ -1 +1,24 @@
|
|||||||
export interface CertificateDTO {}
|
export interface CertificateDTO {
|
||||||
|
name: string;
|
||||||
|
domain: string;
|
||||||
|
cert_type: string;
|
||||||
|
expires_at: string;
|
||||||
|
auto_renew: boolean;
|
||||||
|
id: string
|
||||||
|
created_at: string
|
||||||
|
certificate_pem: string
|
||||||
|
has_private_key: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCertificateDTO {
|
||||||
|
name: string;
|
||||||
|
domain: string;
|
||||||
|
cert_type: string;
|
||||||
|
auto_renew: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditCertificateDTO {
|
||||||
|
name: string
|
||||||
|
domain: string
|
||||||
|
auto_renew: boolean
|
||||||
|
}
|
@@ -1 +1,2 @@
|
|||||||
export * from './duck'
|
export * from './duck'
|
||||||
|
export * from './components'
|
@@ -14,13 +14,13 @@ import { getTemplateById } from '../../duck/api';
|
|||||||
import { protocolOptions } from '../add-template/util';
|
import { protocolOptions } from '../add-template/util';
|
||||||
import { updateTemplate } from '../../duck';
|
import { updateTemplate } from '../../duck';
|
||||||
|
|
||||||
export interface ServerEditProps {
|
export interface TemplateEditProps {
|
||||||
templateId: string;
|
templateId: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: () => void;
|
onOpenChange: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TemplateEdit: FC<ServerEditProps> = (props) => {
|
export const TemplateEdit: FC<TemplateEditProps> = (props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { templateId, isOpen, onOpenChange } = props;
|
const { templateId, isOpen, onOpenChange } = props;
|
||||||
const { register, handleSubmit, reset } = useForm<EditTemplateForm>();
|
const { register, handleSubmit, reset } = useForm<EditTemplateForm>();
|
||||||
@@ -41,12 +41,10 @@ export const TemplateEdit: FC<ServerEditProps> = (props) => {
|
|||||||
default_port: parseInt(values.default_port),
|
default_port: parseInt(values.default_port),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log({data})
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateTemplate({
|
updateTemplate({
|
||||||
id: templateId,
|
id: templateId,
|
||||||
server: data,
|
template: data,
|
||||||
}),
|
}),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
onOpenChange();
|
onOpenChange();
|
||||||
|
@@ -56,12 +56,12 @@ export const updateTemplate = createAsyncThunk(
|
|||||||
async (
|
async (
|
||||||
params: {
|
params: {
|
||||||
id: string;
|
id: string;
|
||||||
server: EditTemplateDTO;
|
template: EditTemplateDTO;
|
||||||
},
|
},
|
||||||
{ dispatch },
|
{ dispatch },
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await patchTemplate(params.id, params.server);
|
await patchTemplate(params.id, params.template);
|
||||||
dispatch(fetchTemplates());
|
dispatch(fetchTemplates());
|
||||||
appNotificator.add({
|
appNotificator.add({
|
||||||
message: 'Template updated',
|
message: 'Template updated',
|
||||||
@@ -85,7 +85,7 @@ export const deleteTemplateAction = createAsyncThunk(
|
|||||||
try {
|
try {
|
||||||
await deleteTemplate(id);
|
await deleteTemplate(id);
|
||||||
appNotificator.add({
|
appNotificator.add({
|
||||||
message: 'Server deleted',
|
message: 'Template deleted',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
dispatch(fetchTemplates());
|
dispatch(fetchTemplates());
|
||||||
|
@@ -15,8 +15,8 @@ export const createTemplate = (params: CreateTemplateDTO) =>
|
|||||||
export const getTemplateById = (id: string) =>
|
export const getTemplateById = (id: string) =>
|
||||||
api.get<string, AxiosResponse<TemplateDTO>>(`/templates/${id}`);
|
api.get<string, AxiosResponse<TemplateDTO>>(`/templates/${id}`);
|
||||||
|
|
||||||
export const patchTemplate = (id: string, server: EditTemplateDTO) =>
|
export const patchTemplate = (id: string, template: EditTemplateDTO) =>
|
||||||
api.put(`/templates/${id}`, server, {
|
api.put(`/templates/${id}`, template, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,45 +1,31 @@
|
|||||||
import type { RouteObject } from 'react-router';
|
import type { RouteObject } from 'react-router';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../common/hooks';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { fetchCertificates, getCertificatesState } from '../../features';
|
||||||
|
import { CreateCertificate } from '../../features/certificates';
|
||||||
|
import { CertificateList } from '../../features/certificates/components/certificate-list';
|
||||||
|
|
||||||
export const Certificates = () => {
|
export const Certificates = () => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const { loading, certificates } = useAppSelector(getCertificatesState)
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
dispatch(fetchCertificates())
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="certificates" className="tab-content active">
|
<div id="certificates" className="tab-content active">
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<h2>Add Certificate</h2>
|
<h2>Add Certificate</h2>
|
||||||
<form id="certificateForm">
|
<CreateCertificate/>
|
||||||
<div className="form-group">
|
|
||||||
<label>Name:</label>
|
|
||||||
<input type="text" id="certName" required />
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Domain:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="certDomain"
|
|
||||||
placeholder="example.com"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>Certificate Type:</label>
|
|
||||||
<select id="certType" required>
|
|
||||||
<option value="self_signed">Self-Signed</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="certAutoRenew" checked /> Auto Renew
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="btn btn-primary">
|
|
||||||
Generate Certificate
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<h2>Certificates List</h2>
|
<h2>Certificates List</h2>
|
||||||
<div id="certificatesList" className="loading">
|
<div id="certificatesList" className="loading">
|
||||||
Loading...
|
{ loading && 'Loading...' }
|
||||||
|
{ certificates.length ? <CertificateList certificates={certificates}/> : <p>No certificates found</p> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user