mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): patch account form complete
This commit is contained in:
parent
a7ea95fd2b
commit
3e0c25158e
@ -18,6 +18,7 @@ import { FaPlus, FaTrash } from 'react-icons/fa'
|
|||||||
import { PageContainer } from '@/components/pageContainer'
|
import { PageContainer } from '@/components/pageContainer'
|
||||||
import { OffCanvas } from '@/components/offcanvas'
|
import { OffCanvas } from '@/components/offcanvas'
|
||||||
import { AccountEdit } from '@/partials/accoutEdit'
|
import { AccountEdit } from '@/partials/accoutEdit'
|
||||||
|
import { get } from 'http'
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [accounts, setAccounts] = useState<CacheAccount[]>([])
|
const [accounts, setAccounts] = useState<CacheAccount[]>([])
|
||||||
@ -34,11 +35,13 @@ export default function Page() {
|
|||||||
|
|
||||||
const fetchAccounts = async () => {
|
const fetchAccounts = async () => {
|
||||||
const newAccounts: CacheAccount[] = []
|
const newAccounts: CacheAccount[] = []
|
||||||
const accounts = await httpService.get<GetAccountResponse[]>(
|
const gatAccountsResult = await httpService.get<GetAccountResponse[]>(
|
||||||
GetApiRoute(ApiRoutes.ACCOUNTS)
|
GetApiRoute(ApiRoutes.ACCOUNTS)
|
||||||
)
|
)
|
||||||
|
|
||||||
accounts?.forEach((account) => {
|
if (!gatAccountsResult.isSuccess) return
|
||||||
|
|
||||||
|
gatAccountsResult.data?.forEach((account) => {
|
||||||
newAccounts.push({
|
newAccounts.push({
|
||||||
accountId: account.accountId,
|
accountId: account.accountId,
|
||||||
isDisabled: account.isDisabled,
|
isDisabled: account.isDisabled,
|
||||||
@ -79,10 +82,15 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteAccount = (accountId: string) => {
|
const deleteAccount = (accountId: string) => {
|
||||||
setAccounts(accounts.filter((account) => account.accountId !== accountId))
|
httpService
|
||||||
|
.delete(GetApiRoute(ApiRoutes.ACCOUNT_ID, accountId))
|
||||||
// TODO: Revoke all certificates
|
.then((response) => {
|
||||||
// TODO: Remove from cache
|
if (response.isSuccess) {
|
||||||
|
setAccounts(
|
||||||
|
accounts.filter((account) => account.accountId !== accountId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -195,7 +203,14 @@ export default function Page() {
|
|||||||
isOpen={editingAccount !== null}
|
isOpen={editingAccount !== null}
|
||||||
onClose={() => setEditingAccount(null)}
|
onClose={() => setEditingAccount(null)}
|
||||||
>
|
>
|
||||||
{editingAccount && <AccountEdit account={editingAccount} />}
|
{editingAccount && (
|
||||||
|
<AccountEdit
|
||||||
|
account={editingAccount}
|
||||||
|
setAccount={setEditingAccount}
|
||||||
|
onCancel={() => setEditingAccount(null)}
|
||||||
|
onDelete={deleteAccount}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</OffCanvas>
|
</OffCanvas>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export interface CustomSelectOption {
|
|||||||
|
|
||||||
export interface CustomSelectPropsBase {
|
export interface CustomSelectPropsBase {
|
||||||
selectedValue: string | null | undefined
|
selectedValue: string | null | undefined
|
||||||
onChange: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
title?: string
|
title?: string
|
||||||
@ -45,7 +45,7 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
|||||||
|
|
||||||
const handleOptionClick = (option: CustomSelectOption) => {
|
const handleOptionClick = (option: CustomSelectOption) => {
|
||||||
if (!readOnly && !disabled) {
|
if (!readOnly && !disabled) {
|
||||||
onChange(option.value)
|
onChange?.(option.value)
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { PatchOperation } from './PatchOperation'
|
||||||
|
|
||||||
export interface PatchAction<T> {
|
export interface PatchAction<T> {
|
||||||
op: PatchOperation // Enum for operation type
|
op: PatchOperation // Enum for operation type
|
||||||
index?: number // Index for the operation (for arrays/lists)
|
index?: number // Index for the operation (for arrays/lists)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
enum PatchOperation {
|
export enum PatchOperation {
|
||||||
Add,
|
Add,
|
||||||
Remove,
|
Remove,
|
||||||
Replace
|
Replace,
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { PatchAction } from '@/models/PatchAction'
|
import { PatchAction } from '@/models/PatchAction'
|
||||||
|
|
||||||
|
export interface PatchHostnameRequest {
|
||||||
|
hostname?: PatchAction<string>
|
||||||
|
isDisabled?: PatchAction<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
export interface PatchAccountRequest {
|
export interface PatchAccountRequest {
|
||||||
description?: PatchAction<string>
|
description?: PatchAction<string>
|
||||||
|
isDisabled?: PatchAction<boolean>
|
||||||
contacts?: PatchAction<string>[]
|
contacts?: PatchAction<string>[]
|
||||||
|
hostnames?: PatchHostnameRequest[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { FormEvent, useCallback, useState } from 'react'
|
import { Dispatch, FormEvent, SetStateAction, useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
useValidation,
|
useValidation,
|
||||||
isValidEmail,
|
isValidEmail,
|
||||||
@ -17,39 +17,71 @@ import {
|
|||||||
import { CacheAccount } from '@/entities/CacheAccount'
|
import { CacheAccount } from '@/entities/CacheAccount'
|
||||||
import { FaPlus, FaTrash } from 'react-icons/fa'
|
import { FaPlus, FaTrash } from 'react-icons/fa'
|
||||||
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
||||||
|
import { deepCopy } from '@/functions'
|
||||||
|
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
||||||
|
import { httpService } from '@/services/httpService'
|
||||||
|
import { PatchAccountRequest } from '@/models/letsEncryptServer/account/requests/PatchAccountRequest'
|
||||||
|
import { PatchOperation } from '@/models/PatchOperation'
|
||||||
|
import { useAppDispatch } from '@/redux/store'
|
||||||
|
import { showToast } from '@/redux/slices/toastSlice'
|
||||||
|
|
||||||
interface AccountEditProps {
|
interface AccountEditProps {
|
||||||
account: CacheAccount
|
account: CacheAccount
|
||||||
|
setAccount: Dispatch<SetStateAction<CacheAccount | null>>
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
onSave?: (account: CacheAccount) => void
|
onSubmit?: (account: CacheAccount) => void
|
||||||
onDelete?: (accountId: string) => void
|
onDelete: (accountId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
const AccountEdit: React.FC<AccountEditProps> = ({
|
||||||
const { account, onCancel, onSave, onDelete } = props
|
account,
|
||||||
|
setAccount,
|
||||||
|
onCancel,
|
||||||
|
onSubmit,
|
||||||
|
onDelete
|
||||||
|
}) => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const [newAccount, setNewAccount] = useState<PatchAccountRequest>({
|
||||||
|
description: { op: PatchOperation.None, value: account.description },
|
||||||
|
isDisabled: { op: PatchOperation.None, value: account.isDisabled },
|
||||||
|
contacts: account.contacts.map((contact) => ({
|
||||||
|
op: PatchOperation.None,
|
||||||
|
value: contact
|
||||||
|
})),
|
||||||
|
hostnames: account.hostnames?.map((hostname) => ({
|
||||||
|
hostname: { op: PatchOperation.None, value: hostname.hostname },
|
||||||
|
isDisabled: { op: PatchOperation.None, value: hostname.isDisabled }
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
const [editingAccount, setEditingAccount] = useState<CacheAccount>(account)
|
|
||||||
const [newContact, setNewContact] = useState('')
|
const [newContact, setNewContact] = useState('')
|
||||||
const [newHostname, setNewHostname] = useState('')
|
const [newHostname, setNewHostname] = useState('')
|
||||||
|
|
||||||
const setDescription = useCallback(
|
useEffect(() => {
|
||||||
(newDescription: string) => {
|
console.log(newAccount)
|
||||||
if (editingAccount) {
|
}, [newAccount])
|
||||||
setEditingAccount({ ...editingAccount, description: newDescription })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[editingAccount]
|
|
||||||
)
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
value: description,
|
value: description,
|
||||||
error: descriptionError,
|
error: descriptionError,
|
||||||
handleChange: handleDescriptionChange,
|
handleChange: handleDescriptionChange
|
||||||
reset: resetDescription
|
|
||||||
} = useValidation<string>({
|
} = useValidation<string>({
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
externalValue: account.description,
|
externalValue: newAccount.description?.value ?? '',
|
||||||
setExternalValue: setDescription,
|
setExternalValue: (newDescription) => {
|
||||||
|
setNewAccount((prev) => {
|
||||||
|
const newAccount = deepCopy(prev)
|
||||||
|
newAccount.description = {
|
||||||
|
op:
|
||||||
|
newDescription !== account.description
|
||||||
|
? PatchOperation.Replace
|
||||||
|
: PatchOperation.None,
|
||||||
|
value: newDescription
|
||||||
|
}
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
|
},
|
||||||
validateFn: isBypass,
|
validateFn: isBypass,
|
||||||
errorMessage: ''
|
errorMessage: ''
|
||||||
})
|
})
|
||||||
@ -81,122 +113,147 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleIsDisabledChange = (value: boolean) => {
|
const handleIsDisabledChange = (value: boolean) => {
|
||||||
// setAccount({ ...account, isDisabled: value })
|
setNewAccount((prev) => {
|
||||||
|
const newAccount = deepCopy(prev)
|
||||||
|
newAccount.isDisabled = {
|
||||||
|
op:
|
||||||
|
value !== account.isDisabled
|
||||||
|
? PatchOperation.Replace
|
||||||
|
: PatchOperation.None,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChallengeTypeChange = (option: any) => {
|
const handleAddContact = () => {
|
||||||
//setAccount({ ...account, challengeType: option.value })
|
if (newContact === '' || contactError) return
|
||||||
|
|
||||||
|
// Check if the contact already exists in the account
|
||||||
|
const contactExists = newAccount.contacts?.some(
|
||||||
|
(contact) => contact.value === newContact
|
||||||
|
)
|
||||||
|
|
||||||
|
if (contactExists) {
|
||||||
|
// Optionally, handle the duplicate contact case, e.g., show an error message
|
||||||
|
dispatch(
|
||||||
|
showToast({ message: 'Contact already exists.', type: 'warning' })
|
||||||
|
)
|
||||||
|
resetContact()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the contact does not exist, add it
|
||||||
|
setNewAccount((prev) => {
|
||||||
|
const newAccount = deepCopy(prev)
|
||||||
|
newAccount.contacts?.push({ op: PatchOperation.Add, value: newContact })
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
|
|
||||||
|
resetContact()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteContact = (contact: string) => {
|
||||||
|
setNewAccount((prev) => {
|
||||||
|
const newAccount = deepCopy(prev)
|
||||||
|
newAccount.contacts = newAccount.contacts
|
||||||
|
?.map((c) => {
|
||||||
|
if (c.value === contact && c.op !== PatchOperation.Add)
|
||||||
|
c.op = PatchOperation.Remove
|
||||||
|
return c
|
||||||
|
})
|
||||||
|
.filter((c) => !(c.value === contact && c.op === PatchOperation.Add))
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddHostname = () => {
|
||||||
|
if (newHostname === '' || hostnameError) return
|
||||||
|
|
||||||
|
// Check if the hostname already exists in the account
|
||||||
|
const hostnameExists = newAccount.hostnames?.some(
|
||||||
|
(hostname) => hostname.hostname?.value === newHostname
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hostnameExists) {
|
||||||
|
// Optionally, handle the duplicate hostname case, e.g., show an error message
|
||||||
|
dispatch(
|
||||||
|
showToast({ message: 'Hostname already exists.', type: 'warning' })
|
||||||
|
)
|
||||||
|
resetHostname()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the hostname does not exist, add it
|
||||||
|
setNewAccount((prev) => {
|
||||||
|
const newAccount = deepCopy(prev)
|
||||||
|
newAccount.hostnames?.push({
|
||||||
|
hostname: { op: PatchOperation.Add, value: newHostname },
|
||||||
|
isDisabled: { op: PatchOperation.Add, value: false }
|
||||||
|
})
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
|
|
||||||
|
resetHostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHostnameDisabledChange = (hostname: string, value: boolean) => {
|
const handleHostnameDisabledChange = (hostname: string, value: boolean) => {
|
||||||
// setAccount({
|
setNewAccount((prev) => {
|
||||||
// ...account,
|
const newAccount = deepCopy(prev)
|
||||||
// hostnames: account.hostnames.map((h) =>
|
const targetHostname = newAccount.hostnames?.find(
|
||||||
// h.hostname === hostname ? { ...h, isDisabled: value } : h
|
(h) => h.hostname?.value === hostname
|
||||||
// )
|
)
|
||||||
// })
|
if (targetHostname) {
|
||||||
// }
|
targetHostname.isDisabled = {
|
||||||
// const handleStagingChange = (value: string) => {
|
op:
|
||||||
// setAccount({ ...account, isStaging: value === 'staging' })
|
value !== targetHostname.isDisabled?.value
|
||||||
|
? PatchOperation.Replace
|
||||||
|
: PatchOperation.None,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteContact = (contact: string) => {
|
const handleDeleteHostname = (hostname: string) => {
|
||||||
if (account?.contacts.length ?? 0 < 1) return
|
setNewAccount((prev) => {
|
||||||
|
const newAccount = deepCopy(prev)
|
||||||
// setAccount({
|
newAccount.hostnames = newAccount.hostnames
|
||||||
// ...account,
|
?.map((h) => {
|
||||||
// contacts: account.contacts.filter((c) => c !== contact)
|
if (
|
||||||
// })
|
h.hostname?.value === hostname &&
|
||||||
// }
|
h.hostname?.op !== PatchOperation.Add
|
||||||
|
)
|
||||||
// const addContact = () => {
|
h.hostname.op = PatchOperation.Remove
|
||||||
// if (newContact === '' || contactError) {
|
return h
|
||||||
// return
|
})
|
||||||
// }
|
.filter(
|
||||||
|
(h) =>
|
||||||
// if (account.contacts.includes(newContact)) return
|
!(
|
||||||
|
h.hostname?.value === hostname &&
|
||||||
// setAccount({ ...account, contacts: [...account.contacts, newContact] })
|
h.hostname?.op === PatchOperation.Add
|
||||||
// resetContact()
|
)
|
||||||
|
)
|
||||||
|
return newAccount
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteHostname = (hostname: string) => {
|
const handleCancel = () => {
|
||||||
//if (account?.hostnames.length ?? 0 < 1) return
|
onCancel?.()
|
||||||
|
|
||||||
// setAccount({
|
|
||||||
// ...account,
|
|
||||||
// hostnames: account.hostnames.filter((h) => h.hostname !== hostname)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const addHostname = () => {
|
|
||||||
// if (newHostname === '' || hostnameError) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (account.hostnames.some((h) => h.hostname === newHostname)) return
|
|
||||||
|
|
||||||
// setAccount({
|
|
||||||
// ...account,
|
|
||||||
// hostnames: [
|
|
||||||
// ...account.hostnames,
|
|
||||||
// {
|
|
||||||
// hostname: newHostname,
|
|
||||||
// expires: new Date(),
|
|
||||||
// isUpcomingExpire: false,
|
|
||||||
// isDisabled: false
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// })
|
|
||||||
resetHostname()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
// const contactChanges = {
|
httpService.patch<PatchAccountRequest, CacheAccount>(
|
||||||
// added: account.contacts.filter(
|
GetApiRoute(ApiRoutes.ACCOUNT_ID, account.accountId),
|
||||||
// (contact) => !initialAccountState.contacts.includes(contact)
|
newAccount
|
||||||
// ),
|
)
|
||||||
// removed: initialAccountState.contacts.filter(
|
}
|
||||||
// (contact) => !account.contacts.includes(contact)
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const hostnameChanges = {
|
const handleDelete = (accountId: string) => {
|
||||||
// added: account.hostnames.filter(
|
onDelete?.(accountId)
|
||||||
// (hostname) =>
|
|
||||||
// !initialAccountState.hostnames.some(
|
|
||||||
// (h) => h.hostname === hostname.hostname
|
|
||||||
// )
|
|
||||||
// ),
|
|
||||||
// removed: initialAccountState.hostnames.filter(
|
|
||||||
// (hostname) =>
|
|
||||||
// !account.hostnames.some((h) => h.hostname === hostname.hostname)
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Handle contact changes
|
|
||||||
// if (contactChanges.added.length > 0) {
|
|
||||||
// // TODO: POST new contacts
|
|
||||||
// console.log('Added contacts:', contactChanges.added)
|
|
||||||
// }
|
|
||||||
// if (contactChanges.removed.length > 0) {
|
|
||||||
// // TODO: DELETE removed contacts
|
|
||||||
// console.log('Removed contacts:', contactChanges.removed)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Handle hostname changes
|
|
||||||
// if (hostnameChanges.added.length > 0) {
|
|
||||||
// // TODO: POST new hostnames
|
|
||||||
// console.log('Added hostnames:', hostnameChanges.added)
|
|
||||||
// }
|
|
||||||
// if (hostnameChanges.removed.length > 0) {
|
|
||||||
// // TODO: DELETE removed hostnames
|
|
||||||
// console.log('Removed hostnames:', hostnameChanges.removed)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// onSave(account)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -217,19 +274,9 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
checked={account.isDisabled}
|
checked={newAccount.isDisabled?.value ?? false}
|
||||||
label="Disabled"
|
label="Disabled"
|
||||||
onChange={(value) => handleIsDisabledChange(value)}
|
onChange={handleIsDisabledChange}
|
||||||
className="mr-2 flex-grow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<CustomEnumSelect
|
|
||||||
title="Challenge Type"
|
|
||||||
enumType={ChallengeTypes}
|
|
||||||
selectedValue={account.challengeType}
|
|
||||||
onChange={(option) => handleChallengeTypeChange(option)}
|
|
||||||
className="mr-2 flex-grow"
|
className="mr-2 flex-grow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -237,22 +284,24 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-xl font-medium mb-2">Contacts:</h3>
|
<h3 className="text-xl font-medium mb-2">Contacts:</h3>
|
||||||
<ul className="list-disc list-inside pl-4 mb-2">
|
<ul className="list-disc list-inside pl-4 mb-2">
|
||||||
{account.contacts.map((contact) => (
|
{newAccount.contacts?.map((contact) => (
|
||||||
<li key={contact} className="text-gray-700 mb-2 inline-flex">
|
<li key={contact.value} className="text-gray-700 mb-2">
|
||||||
{contact}
|
<div className="inline-flex">
|
||||||
<CustomButton
|
{contact.value}
|
||||||
type="button"
|
<CustomButton
|
||||||
onClick={() => deleteContact(contact)}
|
type="button"
|
||||||
className="bg-red-500 text-white p-2 rounded ml-2"
|
onClick={() => handleDeleteContact(contact.value ?? '')}
|
||||||
>
|
className="bg-red-500 text-white p-2 rounded ml-2"
|
||||||
<FaTrash />
|
>
|
||||||
</CustomButton>
|
<FaTrash />
|
||||||
|
</CustomButton>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<CustomInput
|
<CustomInput
|
||||||
value={newContact}
|
value={contact}
|
||||||
onChange={handleContactChange}
|
onChange={handleContactChange}
|
||||||
placeholder="Add new contact"
|
placeholder="Add new contact"
|
||||||
type="email"
|
type="email"
|
||||||
@ -264,43 +313,49 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
//onClick={addContact}
|
onClick={handleAddContact}
|
||||||
className="bg-green-500 text-white p-2 rounded ml-2"
|
className="bg-green-500 text-white p-2 rounded ml-2"
|
||||||
>
|
>
|
||||||
<FaPlus />
|
<FaPlus />
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<CustomEnumSelect
|
||||||
|
title="Challenge Type"
|
||||||
|
enumType={ChallengeTypes}
|
||||||
|
selectedValue={account.challengeType}
|
||||||
|
className="mr-2 flex-grow"
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-medium mb-2">Hostnames:</h3>
|
<h3 className="text-xl font-medium mb-2">Hostnames:</h3>
|
||||||
<ul className="list-disc list-inside pl-4 mb-2">
|
<ul className="list-disc list-inside pl-4 mb-2">
|
||||||
{account.hostnames?.map((hostname) => (
|
{newAccount.hostnames?.map((hostname) => (
|
||||||
<li key={hostname.hostname} className="text-gray-700 mb-2">
|
<li key={hostname.hostname?.value} className="text-gray-700 mb-2">
|
||||||
<div className="inline-flex">
|
<div className="inline-flex">
|
||||||
{hostname.hostname} - {hostname.expires.toDateString()} -{' '}
|
{hostname.hostname?.value} -{' '}
|
||||||
<span
|
|
||||||
className={`ml-2 px-2 py-1 rounded ${
|
|
||||||
hostname.isUpcomingExpire
|
|
||||||
? 'bg-yellow-200 text-yellow-800'
|
|
||||||
: 'bg-green-200 text-green-800'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{hostname.isUpcomingExpire ? 'Upcoming' : 'Not Upcoming'}
|
|
||||||
</span>{' '}
|
|
||||||
-{' '}
|
|
||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
checked={hostname.isDisabled}
|
checked={hostname.isDisabled?.value ?? false}
|
||||||
label="Disabled"
|
label="Disabled"
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
handleHostnameDisabledChange(hostname.hostname, value)
|
handleHostnameDisabledChange(
|
||||||
|
hostname.hostname?.value ?? '',
|
||||||
|
value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => deleteHostname(hostname.hostname)}
|
onClick={() =>
|
||||||
|
handleDeleteHostname(hostname.hostname?.value ?? '')
|
||||||
|
}
|
||||||
className="bg-red-500 text-white p-2 rounded ml-2"
|
className="bg-red-500 text-white p-2 rounded ml-2"
|
||||||
>
|
>
|
||||||
<FaTrash />
|
<FaTrash />
|
||||||
@ -310,7 +365,7 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
</ul>
|
</ul>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<CustomInput
|
<CustomInput
|
||||||
value={newHostname}
|
value={hostname}
|
||||||
onChange={handleHostnameChange}
|
onChange={handleHostnameChange}
|
||||||
placeholder="Add new hostname"
|
placeholder="Add new hostname"
|
||||||
type="text"
|
type="text"
|
||||||
@ -322,7 +377,7 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
//onClick={addHostname}
|
onClick={handleAddHostname}
|
||||||
className="bg-green-500 text-white p-2 rounded ml-2"
|
className="bg-green-500 text-white p-2 rounded ml-2"
|
||||||
>
|
>
|
||||||
<FaPlus />
|
<FaPlus />
|
||||||
@ -345,13 +400,15 @@ const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between mt-4">
|
<div className="flex justify-between mt-4">
|
||||||
<CustomButton
|
<CustomButton
|
||||||
//onClick={() => onDelete(account.accountId)}
|
type="button"
|
||||||
|
onClick={() => handleDelete(account.accountId)}
|
||||||
className="bg-red-500 text-white p-2 rounded ml-2"
|
className="bg-red-500 text-white p-2 rounded ml-2"
|
||||||
>
|
>
|
||||||
<FaTrash />
|
<FaTrash />
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
onClick={onCancel}
|
type="button"
|
||||||
|
onClick={handleCancel}
|
||||||
className="bg-yellow-500 text-white p-2 rounded ml-2"
|
className="bg-yellow-500 text-white p-2 rounded ml-2"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { store } from '@/redux/store'
|
import { store } from '@/redux/store'
|
||||||
import { increment, decrement } from '@/redux/slices/loaderSlice'
|
import { increment, decrement } from '@/redux/slices/loaderSlice'
|
||||||
import { showToast } from '@/redux/slices/toastSlice'
|
import { showToast } from '@/redux/slices/toastSlice'
|
||||||
|
import { PatchOperation } from '@/models/PatchOperation'
|
||||||
|
|
||||||
|
interface HttpResponse<T> {
|
||||||
|
data: T | null
|
||||||
|
status: number
|
||||||
|
isSuccess: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface RequestInterceptor {
|
interface RequestInterceptor {
|
||||||
(req: XMLHttpRequest): void
|
(req: XMLHttpRequest): void
|
||||||
@ -47,7 +54,7 @@ class HttpService {
|
|||||||
method: string,
|
method: string,
|
||||||
url: string,
|
url: string,
|
||||||
data?: any
|
data?: any
|
||||||
): Promise<TResponse | null> {
|
): Promise<HttpResponse<TResponse>> {
|
||||||
const xhr = new XMLHttpRequest()
|
const xhr = new XMLHttpRequest()
|
||||||
xhr.open(method, url)
|
xhr.open(method, url)
|
||||||
|
|
||||||
@ -59,7 +66,7 @@ class HttpService {
|
|||||||
|
|
||||||
this.invokeIncrement()
|
this.invokeIncrement()
|
||||||
|
|
||||||
return new Promise<TResponse | null>((resolve) => {
|
return new Promise<HttpResponse<TResponse>>((resolve) => {
|
||||||
xhr.onload = () => this.handleLoad<TResponse>(xhr, resolve)
|
xhr.onload = () => this.handleLoad<TResponse>(xhr, resolve)
|
||||||
xhr.onerror = () => this.handleNetworkError(resolve)
|
xhr.onerror = () => this.handleNetworkError(resolve)
|
||||||
xhr.send(data ? JSON.stringify(data) : null)
|
xhr.send(data ? JSON.stringify(data) : null)
|
||||||
@ -102,7 +109,7 @@ class HttpService {
|
|||||||
|
|
||||||
private handleLoad<TResponse>(
|
private handleLoad<TResponse>(
|
||||||
xhr: XMLHttpRequest,
|
xhr: XMLHttpRequest,
|
||||||
resolve: (value: TResponse | null) => void
|
resolve: (value: HttpResponse<TResponse>) => void
|
||||||
): void {
|
): void {
|
||||||
this.invokeDecrement()
|
this.invokeDecrement()
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
@ -114,14 +121,22 @@ class HttpService {
|
|||||||
|
|
||||||
private handleSuccessfulResponse<TResponse>(
|
private handleSuccessfulResponse<TResponse>(
|
||||||
xhr: XMLHttpRequest,
|
xhr: XMLHttpRequest,
|
||||||
resolve: (value: TResponse | null) => void
|
resolve: (value: HttpResponse<TResponse>) => void
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
if (xhr.response) {
|
if (xhr.response) {
|
||||||
const response = JSON.parse(xhr.response)
|
const response = JSON.parse(xhr.response)
|
||||||
resolve(this.handleResponseInterceptors(response, null) as TResponse)
|
resolve({
|
||||||
|
data: this.handleResponseInterceptors(response, null) as TResponse,
|
||||||
|
status: xhr.status,
|
||||||
|
isSuccess: true
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
resolve(null)
|
resolve({
|
||||||
|
data: null,
|
||||||
|
status: xhr.status,
|
||||||
|
isSuccess: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const problemDetails = this.createProblemDetails(
|
const problemDetails = this.createProblemDetails(
|
||||||
@ -130,13 +145,17 @@ class HttpService {
|
|||||||
xhr.status
|
xhr.status
|
||||||
)
|
)
|
||||||
this.showProblemDetails(problemDetails)
|
this.showProblemDetails(problemDetails)
|
||||||
resolve(null)
|
resolve({
|
||||||
|
data: null,
|
||||||
|
status: xhr.status,
|
||||||
|
isSuccess: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleErrorResponse<TResponse>(
|
private handleErrorResponse<TResponse>(
|
||||||
xhr: XMLHttpRequest,
|
xhr: XMLHttpRequest,
|
||||||
resolve: (value: TResponse | null) => void
|
resolve: (value: HttpResponse<TResponse>) => void
|
||||||
): void {
|
): void {
|
||||||
const problemDetails = this.createProblemDetails(
|
const problemDetails = this.createProblemDetails(
|
||||||
xhr.statusText,
|
xhr.statusText,
|
||||||
@ -144,15 +163,23 @@ class HttpService {
|
|||||||
xhr.status
|
xhr.status
|
||||||
)
|
)
|
||||||
this.showProblemDetails(problemDetails)
|
this.showProblemDetails(problemDetails)
|
||||||
resolve(this.handleResponseInterceptors(null, problemDetails))
|
resolve({
|
||||||
|
data: this.handleResponseInterceptors(null, problemDetails),
|
||||||
|
status: xhr.status,
|
||||||
|
isSuccess: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNetworkError<TResponse>(
|
private handleNetworkError<TResponse>(
|
||||||
resolve: (value: TResponse | null) => void
|
resolve: (value: HttpResponse<TResponse>) => void
|
||||||
): void {
|
): void {
|
||||||
const problemDetails = this.createProblemDetails('Network Error', null, 0)
|
const problemDetails = this.createProblemDetails('Network Error', null, 0)
|
||||||
this.showProblemDetails(problemDetails)
|
this.showProblemDetails(problemDetails)
|
||||||
resolve(this.handleResponseInterceptors(null, problemDetails))
|
resolve({
|
||||||
|
data: this.handleResponseInterceptors(null, problemDetails),
|
||||||
|
status: 0,
|
||||||
|
isSuccess: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private createProblemDetails(
|
private createProblemDetails(
|
||||||
@ -178,26 +205,57 @@ class HttpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get<TResponse>(url: string): Promise<TResponse | null> {
|
public async get<TResponse>(url: string): Promise<HttpResponse<TResponse>> {
|
||||||
return await this.request<TResponse>('GET', url)
|
return await this.request<TResponse>('GET', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async post<TRequest, TResponse>(
|
public async post<TRequest, TResponse>(
|
||||||
url: string,
|
url: string,
|
||||||
data: TRequest
|
data: TRequest
|
||||||
): Promise<TResponse | null> {
|
): Promise<HttpResponse<TResponse>> {
|
||||||
return await this.request<TResponse>('POST', url, data)
|
return await this.request<TResponse>('POST', url, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async put<TRequest, TResponse>(
|
public async put<TRequest, TResponse>(
|
||||||
url: string,
|
url: string,
|
||||||
data: TRequest
|
data: TRequest
|
||||||
): Promise<TResponse | null> {
|
): Promise<HttpResponse<TResponse>> {
|
||||||
return await this.request<TResponse>('PUT', url, data)
|
return await this.request<TResponse>('PUT', url, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete<TResponse>(url: string): Promise<TResponse | null> {
|
private cleanPatchRequest(obj: any): any {
|
||||||
return await this.request<TResponse>('DELETE', url)
|
if (Array.isArray(obj)) {
|
||||||
|
const cleanedArray = obj
|
||||||
|
.map(this.cleanPatchRequest)
|
||||||
|
.filter((item) => item !== null && item !== undefined)
|
||||||
|
return cleanedArray.length > 0 ? cleanedArray : null
|
||||||
|
} else if (typeof obj === 'object' && obj !== null) {
|
||||||
|
if (obj.op !== undefined && obj.op === PatchOperation.None) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanedObject: any = {}
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const cleanedValue = this.cleanPatchRequest(obj[key])
|
||||||
|
if (cleanedValue !== null) {
|
||||||
|
cleanedObject[key] = cleanedValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Object.keys(cleanedObject).length > 0 ? cleanedObject : null
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
public async patch<TRequest, TResponse>(
|
||||||
|
url: string,
|
||||||
|
data: TRequest
|
||||||
|
): Promise<HttpResponse<TResponse>> {
|
||||||
|
const cleanedData = this.cleanPatchRequest(data)
|
||||||
|
return await this.request<TResponse>('PATCH', url, cleanedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(url: string): Promise<HttpResponse<null>> {
|
||||||
|
return await this.request<null>('DELETE', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRequestInterceptor(interceptor: RequestInterceptor): void {
|
public addRequestInterceptor(interceptor: RequestInterceptor): void {
|
||||||
@ -233,10 +291,10 @@ export { httpService }
|
|||||||
|
|
||||||
// Example usage of the httpService
|
// Example usage of the httpService
|
||||||
// async function fetchData() {
|
// async function fetchData() {
|
||||||
// const data = await httpService.get<any>('/api/data');
|
// const response = await httpService.get<any>('/api/data');
|
||||||
// if (data) {
|
// if (response.isSuccess) {
|
||||||
// console.log('Data received:', data);
|
// console.log('Data received:', response.data);
|
||||||
// } else {
|
// } else {
|
||||||
// console.error('Failed to fetch data');
|
// console.error('Failed to fetch data, status code:', response.status);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -182,6 +182,9 @@ public class AccountService : IAccountService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IDomainResult> DeleteAccountAsync(Guid accountId) {
|
public async Task<IDomainResult> DeleteAccountAsync(Guid accountId) {
|
||||||
|
// TODO: Revoke all certificates
|
||||||
|
|
||||||
|
// Remove from cache
|
||||||
return await _cacheService.DeleteFromCacheAsync(accountId);
|
return await _cacheService.DeleteFromCacheAsync(accountId);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -216,10 +216,11 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
public (string?, IDomainResult) AcmeChallenge(string fileName) {
|
public (string?, IDomainResult) AcmeChallenge(string fileName) {
|
||||||
DeleteExporedChallenges();
|
DeleteExporedChallenges();
|
||||||
|
|
||||||
var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName));
|
var challengePath = Path.Combine(_acmePath, fileName);
|
||||||
if (fileContent == null)
|
if(!File.Exists(challengePath))
|
||||||
return IDomainResult.NotFound<string?>();
|
return IDomainResult.NotFound<string?>();
|
||||||
|
|
||||||
|
var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName));
|
||||||
return IDomainResult.Success(fileContent);
|
return IDomainResult.Success(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user