mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): registration page init
This commit is contained in:
parent
2dfc7259fb
commit
e321a3237f
@ -1,25 +1,21 @@
|
||||
enum ApiRoutes {
|
||||
|
||||
CACHE_GET_ACCOUNTS = `api/Cache/GetAccounts`,
|
||||
|
||||
CACHE_GET_CONTACTS = `api/Cache/GetContacts/{accountId}`,
|
||||
CACHE_ADD_CONTACT = `api/Cache/AddContact/{accountId}`,
|
||||
CACHE_DELETE_CONTACT = `api/Cache/DeleteContact/{accountId}?contact={contact}`,
|
||||
CACHE_ACCOUNTS = 'api/cache/accounts',
|
||||
CACHE_ACCOUNT = 'api/cache/account/{accountId}',
|
||||
CACHE_ACCOUNT_CONTACTS = 'api/cache/account/{accountId}/contacts',
|
||||
CACHE_ACCOUNT_CONTACT = 'api/cache/account/{accountId}/contacts/{index}',
|
||||
CACHE_ACCOUNT_HOSTNAMES = 'api/cache/account/{accountId}/hostnames',
|
||||
|
||||
|
||||
CACHE_GET_HOSTNAMES = `api/Cache/GetHostnames/{accountId}`,
|
||||
// TODO: here is different flow via CertsFlowController, cache update is the result of add order and invalidate cert
|
||||
// CACHE_ADD_HOSTNAME = `api/Cache/AddHostname/{accountId}`,
|
||||
// CACHE_DELETE_HOSTNAME = `api/Cache/DeleteHostname/{accountId}?hostname={hostname}`,
|
||||
|
||||
CERTS_FLOW_CONFIGURE_CLIENT = `api/CertsFlow/ConfigureClient`,
|
||||
CERTS_FLOW_TERMS_OF_SERVICE = `api/CertsFlow/TermsOfService/{sessionId}`,
|
||||
CERTS_FLOW_INIT = `api/CertsFlow/Init/{sessionId}/{accountId}`,
|
||||
CERTS_FLOW_NEW_ORDER = `api/CertsFlow/NewOrder/{sessionId}`,
|
||||
CERTS_FLOW_GET_ORDER = `api/CertsFlow/GetOrder/{sessionId}`,
|
||||
CERTS_FLOW_GET_CERTIFICATES = `api/CertsFlow/GetCertificates/{sessionId}`,
|
||||
CERTS_FLOW_APPLY_CERTIFICATES = `api/CertsFlow/ApplyCertificates/{sessionId}`,
|
||||
CERTS_FLOW_HOSRS_WITH_UPCOMING_SSL_EXPIRY = `api/CertsFlow/HostsWithUpcomingSslExpiry/{sessionId}`
|
||||
// CERTS_FLOW_CONFIGURE_CLIENT = `api/CertsFlow/ConfigureClient`,
|
||||
// CERTS_FLOW_TERMS_OF_SERVICE = `api/CertsFlow/TermsOfService/{sessionId}`,
|
||||
// CERTS_FLOW_INIT = `api/CertsFlow/Init/{sessionId}/{accountId}`,
|
||||
// CERTS_FLOW_NEW_ORDER = `api/CertsFlow/NewOrder/{sessionId}`,
|
||||
// CERTS_FLOW_GET_ORDER = `api/CertsFlow/GetOrder/{sessionId}`,
|
||||
// CERTS_FLOW_GET_CERTIFICATES = `api/CertsFlow/GetCertificates/{sessionId}`,
|
||||
// CERTS_FLOW_APPLY_CERTIFICATES = `api/CertsFlow/ApplyCertificates/{sessionId}`,
|
||||
// CERTS_FLOW_HOSRS_WITH_UPCOMING_SSL_EXPIRY = `api/CertsFlow/HostsWithUpcomingSslExpiry/{sessionId}`
|
||||
}
|
||||
|
||||
const GetApiRoute = (route: ApiRoutes, ...args: string[]): string => {
|
||||
@ -27,7 +23,7 @@ const GetApiRoute = (route: ApiRoutes, ...args: string[]): string => {
|
||||
args.forEach(arg => {
|
||||
result = result.replace(/{.*?}/, arg);
|
||||
});
|
||||
return 'http://localhost:5000/' + result;
|
||||
return `http://localhost:5000/${result}`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
"use client" // Add this line
|
||||
"use client"
|
||||
|
||||
import React, { FC, useState, useEffect, useRef } from 'react'
|
||||
import './globals.css'
|
||||
import { SideMenu } from '@/components/sidemenu'
|
||||
import { TopMenu } from '@/components/topmenu'
|
||||
import { Footer } from '@/components/footer'
|
||||
import { OffCanvas } from '@/components/offcanvas'
|
||||
import { Loader } from '@/components/loader' // Import the Loader component
|
||||
import { Loader } from '@/components/loader'
|
||||
import { Metadata } from 'next'
|
||||
import { Toast } from '@/components/toast'
|
||||
import { Provider } from 'react-redux'
|
||||
import { store } from '@/redux/store'
|
||||
import './globals.css'
|
||||
|
||||
const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
@ -20,7 +20,6 @@ const metadata: Metadata = {
|
||||
const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
|
||||
const [isManuallyCollapsed, setManuallyCollapsed] = useState(false)
|
||||
//const [isLoading, setIsLoading] = useState(true) // State to control the loader visibility
|
||||
|
||||
const init = useRef(false)
|
||||
|
||||
@ -43,7 +42,6 @@ const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
} else {
|
||||
if (isManuallyCollapsed) return
|
||||
|
||||
// Reset manualCollapse if the window is resized to a state that should automatically collapse/expand the sidebar
|
||||
if (window.innerWidth > 768 && isSidebarCollapsed) {
|
||||
setIsSidebarCollapsed(false)
|
||||
} else if (window.innerWidth <= 768 && !isSidebarCollapsed) {
|
||||
@ -54,7 +52,7 @@ const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
handleResize() // Set the initial state based on the current window width
|
||||
handleResize()
|
||||
init.current = true
|
||||
}
|
||||
|
||||
@ -72,20 +70,23 @@ const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="h-screen overflow-hidden">
|
||||
<body className="h-screen overflow-hidden flex flex-col">
|
||||
<Provider store={store}>
|
||||
<Loader />
|
||||
|
||||
<div className="flex h-full">
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<SideMenu isCollapsed={isSidebarCollapsed} toggleSidebar={manuallyToggleSidebar} />
|
||||
<div className="flex flex-col flex-1">
|
||||
|
||||
<div className="flex flex-col flex-1 overflow-hidden">
|
||||
<TopMenu onToggleOffCanvas={toggleOffCanvas} />
|
||||
<main className="flex-1 p-4 transition-transform duration-300">
|
||||
<main className="flex-1 p-4 overflow-y-auto">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
<Footer className="flex-shrink-0" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<OffCanvas isOpen={isOffCanvasOpen} onClose={toggleOffCanvas} />
|
||||
<Toast />
|
||||
</Provider>
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import { ApiRoutes, GetApiRoute } from "@/ApiRoutes"
|
||||
import { GetAccountsResponse } from "@/models/letsEncryptServer/cache/GetAccountsResponse"
|
||||
import { GetContactsResponse } from "@/models/letsEncryptServer/cache/GetContactsResponse"
|
||||
import { GetHostnamesResponse } from "@/models/letsEncryptServer/cache/GetHostnamesResponse"
|
||||
import { httpService } from "@/services/httpService"
|
||||
import { FormEvent, useEffect, useRef, useState } from "react"
|
||||
import { useValidation, isValidEmail, isValidHostname } from "@/hooks/useValidation"
|
||||
import { CustomButton, CustomInput } from "@/controls"
|
||||
import { TrashIcon, PlusIcon } from "@heroicons/react/24/solid"
|
||||
import { GetAccountResponse } from "@/models/letsEncryptServer/cache/responses/GetAccountResponse"
|
||||
|
||||
interface CacheAccountHostname {
|
||||
hostname: string
|
||||
@ -32,39 +30,44 @@ export default function Page() {
|
||||
value: newContact,
|
||||
error: contactError,
|
||||
handleChange: handleContactChange
|
||||
} = useValidation("", isValidEmail, "Invalid email format.")
|
||||
} = useValidation({
|
||||
initialValue:"",
|
||||
validateFn: isValidEmail,
|
||||
errorMessage: "Invalid email format."
|
||||
})
|
||||
const {
|
||||
value: newHostname,
|
||||
error: hostnameError,
|
||||
handleChange: handleHostnameChange
|
||||
} = useValidation("", isValidHostname, "Invalid hostname format.")
|
||||
} = useValidation({
|
||||
initialValue: "",
|
||||
validateFn: isValidHostname,
|
||||
errorMessage: "Invalid hostname format."})
|
||||
|
||||
const init = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (init.current) return
|
||||
|
||||
|
||||
console.log("Fetching accounts")
|
||||
|
||||
const fetchAccounts = async () => {
|
||||
const newAccounts: CacheAccount[] = []
|
||||
const accountsResponse = await httpService.get<GetAccountsResponse>(GetApiRoute(ApiRoutes.CACHE_GET_ACCOUNTS))
|
||||
|
||||
for (const accountId of accountsResponse.accountIds) {
|
||||
const [contactsResponse, hostnamesResponse] = await Promise.all([
|
||||
httpService.get<GetContactsResponse>(GetApiRoute(ApiRoutes.CACHE_GET_CONTACTS, accountId)),
|
||||
httpService.get<GetHostnamesResponse>(GetApiRoute(ApiRoutes.CACHE_GET_HOSTNAMES, accountId))
|
||||
])
|
||||
const accounts = await httpService.get<GetAccountResponse []>(GetApiRoute(ApiRoutes.CACHE_ACCOUNTS))
|
||||
|
||||
accounts?.forEach((account) => {
|
||||
newAccounts.push({
|
||||
accountId: accountId,
|
||||
contacts: contactsResponse.contacts,
|
||||
hostnames: hostnamesResponse.hostnames.map(h => ({
|
||||
accountId: account.accountId,
|
||||
contacts: account.contacts,
|
||||
hostnames: account.hostnames.map(h => ({
|
||||
hostname: h.hostname,
|
||||
expires: new Date(h.expires),
|
||||
isUpcomingExpire: h.isUpcomingExpire
|
||||
})),
|
||||
isEditMode: false
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
setAccounts(newAccounts)
|
||||
setInitialAccounts(JSON.parse(JSON.stringify(newAccounts))) // Clone initial state
|
||||
@ -92,7 +95,7 @@ export default function Page() {
|
||||
if (account?.contacts.length ?? 0 < 1) return
|
||||
|
||||
// TODO: Remove from cache
|
||||
httpService.delete(GetApiRoute(ApiRoutes.CACHE_DELETE_CONTACT, accountId, contact))
|
||||
httpService.delete(GetApiRoute(ApiRoutes.CACHE_ACCOUNT_CONTACT, accountId, contact))
|
||||
|
||||
setAccounts(accounts.map(account =>
|
||||
account.accountId === accountId
|
||||
@ -192,7 +195,7 @@ export default function Page() {
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-4xl font-bold text-center mb-8">LetsEncrypt Client Dashboard</h1>
|
||||
<h1 className="text-4xl font-bold text-center mb-8">LetsEncrypt Auto Renew</h1>
|
||||
{
|
||||
accounts.map(account => (
|
||||
<div key={account.accountId} className="bg-white shadow-lg rounded-lg p-6 mb-6">
|
||||
|
||||
209
src/ClientApp/app/register/page.tsx
Normal file
209
src/ClientApp/app/register/page.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
"use client"
|
||||
|
||||
import { ApiRoutes, GetApiRoute } from "@/ApiRoutes"
|
||||
import { httpService } from "@/services/httpService"
|
||||
import { FormEvent, useEffect, useRef, useState } from "react"
|
||||
import { useValidation, isValidContact, isValidHostname } from "@/hooks/useValidation"
|
||||
import { CustomButton, CustomInput } from "@/controls"
|
||||
import { FaTrash, FaPlus } from "react-icons/fa"
|
||||
import { GetAccountResponse } from "@/models/letsEncryptServer/cache/responses/GetAccountResponse"
|
||||
|
||||
interface CacheAccountHostname {
|
||||
hostname: string
|
||||
expires: Date
|
||||
isUpcomingExpire: boolean
|
||||
}
|
||||
|
||||
interface CacheAccount {
|
||||
accountId: string
|
||||
description?: string
|
||||
contacts: string[]
|
||||
hostnames: CacheAccountHostname[]
|
||||
isEditMode: boolean
|
||||
}
|
||||
|
||||
const RegisterPage = () => {
|
||||
const [accounts, setAccounts] = useState<CacheAccount[]>([])
|
||||
const [initialAccounts, setInitialAccounts] = useState<CacheAccount[]>([])
|
||||
const [description, setDescription] = useState("")
|
||||
const [contacts, setContacts] = useState<string[]>([])
|
||||
const [hostnames, setHostnames] = useState<string[]>([])
|
||||
|
||||
const {
|
||||
value: newContact,
|
||||
error: contactError,
|
||||
handleChange: handleContactChange,
|
||||
reset: resetContact
|
||||
} = useValidation({
|
||||
initialValue: "",
|
||||
validateFn: isValidContact,
|
||||
errorMessage: "Invalid contact. Must be a valid email or phone number."
|
||||
})
|
||||
|
||||
const {
|
||||
value: newHostname,
|
||||
error: hostnameError,
|
||||
handleChange: handleHostnameChange,
|
||||
reset: resetHostname
|
||||
} = useValidation({
|
||||
initialValue: "",
|
||||
validateFn: isValidHostname,
|
||||
errorMessage: "Invalid hostname format."
|
||||
})
|
||||
|
||||
const init = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (init.current) return
|
||||
|
||||
const fetchAccounts = async () => {
|
||||
const newAccounts: CacheAccount[] = []
|
||||
const accounts = await httpService.get<GetAccountResponse[]>(GetApiRoute(ApiRoutes.CACHE_ACCOUNTS))
|
||||
|
||||
accounts?.forEach((account) => {
|
||||
newAccounts.push({
|
||||
accountId: account.accountId,
|
||||
contacts: account.contacts,
|
||||
hostnames: account.hostnames.map(h => ({
|
||||
hostname: h.hostname,
|
||||
expires: new Date(h.expires),
|
||||
isUpcomingExpire: h.isUpcomingExpire
|
||||
})),
|
||||
isEditMode: false
|
||||
})
|
||||
})
|
||||
|
||||
setAccounts(newAccounts)
|
||||
setInitialAccounts(JSON.parse(JSON.stringify(newAccounts))) // Clone initial state
|
||||
}
|
||||
|
||||
fetchAccounts()
|
||||
init.current = true
|
||||
}, [])
|
||||
|
||||
const handleAddContact = () => {
|
||||
if (newContact.trim() !== "" && !contactError) {
|
||||
setContacts([...contacts, newContact.trim()])
|
||||
resetContact()
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddHostname = () => {
|
||||
if (newHostname.trim() !== "" && !hostnameError) {
|
||||
setHostnames([...hostnames, newHostname.trim()])
|
||||
resetHostname()
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteContact = (contact: string) => {
|
||||
setContacts(contacts.filter(c => c !== contact))
|
||||
}
|
||||
|
||||
const handleDeleteHostname = (hostname: string) => {
|
||||
setHostnames(hostnames.filter(h => h !== hostname))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!description || contacts.length === 0 || hostnames.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const newAccount = {
|
||||
description,
|
||||
contacts,
|
||||
hostnames: hostnames.map(hostname => ({ hostname, expires: new Date(), isUpcomingExpire: false }))
|
||||
}
|
||||
|
||||
// TODO: Implement API call to create new account
|
||||
console.log("New account data:", newAccount)
|
||||
|
||||
// Reset form fields
|
||||
setDescription("")
|
||||
setContacts([])
|
||||
setHostnames([])
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-4xl font-bold text-center mb-8">Register LetsEncrypt Account</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-4">
|
||||
<CustomInput
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Account Description"
|
||||
title="Description"
|
||||
inputClassName="border p-2 rounded w-full"
|
||||
className="mb-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xl font-medium mb-2">Contacts:</h3>
|
||||
<ul className="list-disc list-inside pl-4 mb-2">
|
||||
{contacts.map(contact => (
|
||||
<li key={contact} className="text-gray-700 flex justify-between items-center mb-2">
|
||||
{contact}
|
||||
<button type="button" onClick={() => handleDeleteContact(contact)} className="bg-red-500 text-white px-2 py-1 rounded ml-4">
|
||||
<FaTrash />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center mb-4">
|
||||
<CustomInput
|
||||
value={newContact}
|
||||
onChange={handleContactChange}
|
||||
placeholder="Add contact"
|
||||
type="text"
|
||||
error={contactError}
|
||||
title="New Contact"
|
||||
inputClassName="border p-2 rounded w-full"
|
||||
errorClassName="text-red-500 text-sm mt-1"
|
||||
className="mr-2 flex-grow"
|
||||
/>
|
||||
<button type="button" onClick={handleAddContact} className="bg-green-500 text-white p-2 rounded ml-2 h-10 flex items-center">
|
||||
<FaPlus />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xl font-medium mb-2">Hostnames:</h3>
|
||||
<ul className="list-disc list-inside pl-4 mb-2">
|
||||
{hostnames.map(hostname => (
|
||||
<li key={hostname} className="text-gray-700 flex justify-between items-center mb-2">
|
||||
{hostname}
|
||||
<button type="button" onClick={() => handleDeleteHostname(hostname)} className="bg-red-500 text-white px-2 py-1 rounded ml-4">
|
||||
<FaTrash />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center">
|
||||
<CustomInput
|
||||
value={newHostname}
|
||||
onChange={handleHostnameChange}
|
||||
placeholder="Add hostname"
|
||||
type="text"
|
||||
error={hostnameError}
|
||||
title="New Hostname"
|
||||
inputClassName="border p-2 rounded w-full"
|
||||
errorClassName="text-red-500 text-sm mt-1"
|
||||
className="mr-2 flex-grow"
|
||||
/>
|
||||
<button type="button" onClick={handleAddHostname} className="bg-green-500 text-white p-2 rounded ml-2 h-10 flex items-center">
|
||||
<FaPlus />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton type="submit" className="bg-green-500 text-white px-3 py-1 rounded">
|
||||
Create Account
|
||||
</CustomButton>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterPage
|
||||
@ -1,9 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
const Footer = () => {
|
||||
|
||||
interface FooterProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Footer = (props: FooterProps) => {
|
||||
|
||||
const { className } = props
|
||||
return (
|
||||
<footer className="bg-gray-900 text-white text-center p-4">
|
||||
<p>© {new Date().getFullYear()} MAKS-IT</p>
|
||||
<footer className={`bg-gray-900 text-white text-center p-4 ${className}`}>
|
||||
<p>{`© ${new Date().getFullYear()} MAKS-IT`}</p>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -30,8 +30,7 @@ const Loader: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="loader-overlay">
|
||||
<div className="spinner"></div>
|
||||
<div className="loading-text">Loading...</div>
|
||||
<span className="loader"></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -10,25 +10,40 @@
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 8px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 8px solid #3498db;
|
||||
.loader {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.loader::after,
|
||||
.loader::before {
|
||||
content: '';
|
||||
box-sizing: border-box;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
background: #FFF;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation: animloader 2s linear infinite;
|
||||
}
|
||||
.loader::after {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20px;
|
||||
font-size: 1.2em;
|
||||
color: #3498db;
|
||||
@keyframes animloader {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,41 +1,42 @@
|
||||
import React, { FC, useEffect, useRef } from 'react'
|
||||
import { FaHome, FaUser, FaCog, FaBars } from 'react-icons/fa'
|
||||
import React, { FC } from 'react';
|
||||
import { FaHome, FaUserPlus, FaBars, FaSyncAlt } from 'react-icons/fa';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface SideMenuProps {
|
||||
isCollapsed: boolean
|
||||
toggleSidebar: () => void
|
||||
isCollapsed: boolean;
|
||||
toggleSidebar: () => void;
|
||||
}
|
||||
|
||||
const SideMenu: FC<SideMenuProps> = ({ isCollapsed, toggleSidebar }) => {
|
||||
const menuItems = [
|
||||
{ icon: <FaSyncAlt />, label: 'Auto Renew', path: '/' },
|
||||
{ icon: <FaUserPlus />, label: 'Register', path: '/register' }
|
||||
];
|
||||
|
||||
const SideMenu: FC<SideMenuProps> = ({ isCollapsed, toggleSidebar }) => {
|
||||
return (
|
||||
<div className={`flex flex-col bg-gray-800 text-white transition-all duration-300 ${isCollapsed ? 'w-16' : 'w-64'} h-full`}>
|
||||
<div className="flex items-center h-16 bg-gray-900 relative">
|
||||
<button onClick={toggleSidebar} className="absolute left-4">
|
||||
<FaBars />
|
||||
</button>
|
||||
<h1 className={`${isCollapsed ? 'hidden' : 'block'} text-2xl font-bold ml-12`}>Logo</h1>
|
||||
<h1 className={`${isCollapsed ? 'hidden' : 'block'} text-2xl font-bold ml-12`}>Certs UI</h1>
|
||||
</div>
|
||||
<nav className="flex-1">
|
||||
<ul>
|
||||
<li className="flex items-center p-4 hover:bg-gray-700">
|
||||
<FaHome className="mr-4" />
|
||||
<span className={`${isCollapsed ? 'hidden' : 'block'}`}>Home</span>
|
||||
</li>
|
||||
<li className="flex items-center p-4 hover:bg-gray-700">
|
||||
<FaUser className="mr-4" />
|
||||
<span className={`${isCollapsed ? 'hidden' : 'block'}`}>Profile</span>
|
||||
</li>
|
||||
<li className="flex items-center p-4 hover:bg-gray-700">
|
||||
<FaCog className="mr-4" />
|
||||
<span className={`${isCollapsed ? 'hidden' : 'block'}`}>Settings</span>
|
||||
{menuItems.map((item, index) => (
|
||||
<li key={index} className="hover:bg-gray-700">
|
||||
<Link href={item.path} className="flex items-center w-full p-4">
|
||||
<span className={`${isCollapsed ? 'mr-0' : 'mr-4'}`}>{item.icon}</span>
|
||||
<span className={`${isCollapsed ? 'hidden' : 'block'}`}>{item.label}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
SideMenu
|
||||
}
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@ import React from 'react'
|
||||
|
||||
interface CustomInputProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
onChange?: (value: string) => void
|
||||
placeholder?: string
|
||||
type: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url'
|
||||
error?: string
|
||||
@ -25,13 +25,18 @@ const CustomInput: React.FC<CustomInputProps> = ({
|
||||
errorClassName = '',
|
||||
className = ''
|
||||
}) => {
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange?.(e.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{title && <label>{title}</label>}
|
||||
<input
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
className={inputClassName}
|
||||
/>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { CustomButton } from "./customButton"
|
||||
import { CustomInput } from "./customInput"
|
||||
import { CustomButton } from "./customButton";
|
||||
import { CustomInput } from "./customInput";
|
||||
|
||||
export {
|
||||
CustomButton,
|
||||
|
||||
@ -6,17 +6,35 @@ const isValidEmail = (email: string) => {
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
const isValidPhoneNumber = (phone: string) => {
|
||||
const phoneRegex = /^\+?[1-9]\d{1,14}$/
|
||||
return phoneRegex.test(phone)
|
||||
}
|
||||
|
||||
const isValidContact = (contact: string) => {
|
||||
return isValidEmail(contact) || isValidPhoneNumber(contact)
|
||||
}
|
||||
|
||||
const isValidHostname = (hostname: string) => {
|
||||
const hostnameRegex = /^(?!:\/\/)([a-zA-Z0-9-_]{1,63}\.?)+[a-zA-Z]{2,6}$/
|
||||
return hostnameRegex.test(hostname)
|
||||
}
|
||||
|
||||
// Props interface for useValidation hook
|
||||
interface UseValidationProps {
|
||||
initialValue: string
|
||||
validateFn: (value: string) => boolean
|
||||
errorMessage: string
|
||||
}
|
||||
|
||||
// Custom hook for input validation
|
||||
const useValidation = (initialValue: string, validateFn: (value: string) => boolean, errorMessage: string) => {
|
||||
const useValidation = ({ initialValue, validateFn, errorMessage }: UseValidationProps) => {
|
||||
const [value, setValue] = useState(initialValue)
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
|
||||
console.log(newValue)
|
||||
setValue(newValue)
|
||||
if (newValue.trim() === "") {
|
||||
setError("This field cannot be empty.")
|
||||
@ -31,7 +49,7 @@ const useValidation = (initialValue: string, validateFn: (value: string) => bool
|
||||
handleChange(initialValue)
|
||||
}, [initialValue])
|
||||
|
||||
return { value, error, handleChange }
|
||||
return { value, error, handleChange, reset: () => setValue("") }
|
||||
}
|
||||
|
||||
export { useValidation, isValidEmail, isValidHostname }
|
||||
export { useValidation, isValidEmail, isValidPhoneNumber, isValidContact, isValidHostname }
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export interface GetAccountsResponse {
|
||||
accountIds: string[]
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
interface HostnameResponse {
|
||||
hostname: string
|
||||
expires: string,
|
||||
isUpcomingExpire: boolean
|
||||
}
|
||||
|
||||
export interface GetHostnamesResponse {
|
||||
hostnames: HostnameResponse[]
|
||||
}
|
||||
9
src/ClientApp/models/letsEncryptServer/cache/responses/GetAccountResponse.ts
vendored
Normal file
9
src/ClientApp/models/letsEncryptServer/cache/responses/GetAccountResponse.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { HostnameResponse } from "./HostnameResponse";
|
||||
|
||||
export interface GetAccountResponse {
|
||||
accountId: string,
|
||||
contacts: string[],
|
||||
hostnames: HostnameResponse[],
|
||||
}
|
||||
|
||||
|
||||
5
src/ClientApp/models/letsEncryptServer/cache/responses/GetHostnamesResponse.ts
vendored
Normal file
5
src/ClientApp/models/letsEncryptServer/cache/responses/GetHostnamesResponse.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import { HostnameResponse } from "./HostnameResponse";
|
||||
|
||||
export interface GetHostnamesResponse {
|
||||
hostnames: HostnameResponse[]
|
||||
}
|
||||
5
src/ClientApp/models/letsEncryptServer/cache/responses/HostnameResponse.ts
vendored
Normal file
5
src/ClientApp/models/letsEncryptServer/cache/responses/HostnameResponse.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export interface HostnameResponse {
|
||||
hostname: string
|
||||
expires: string
|
||||
isUpcomingExpire: boolean
|
||||
}
|
||||
@ -1,55 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class LockManager : IDisposable {
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
|
||||
public LockManager(int initialCount, int maxCount) {
|
||||
_semaphore = new SemaphoreSlim(initialCount, maxCount);
|
||||
}
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly ConcurrentDictionary<int, int> _reentrantCounts = new ConcurrentDictionary<int, int>();
|
||||
|
||||
public async Task<T> ExecuteWithLockAsync<T>(Func<Task<T>> action) {
|
||||
var threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
if (!_reentrantCounts.ContainsKey(threadId)) {
|
||||
_reentrantCounts[threadId] = 0;
|
||||
}
|
||||
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
await _semaphore.WaitAsync();
|
||||
}
|
||||
|
||||
_reentrantCounts[threadId]++;
|
||||
try {
|
||||
return await action();
|
||||
}
|
||||
finally {
|
||||
_reentrantCounts[threadId]--;
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteWithLockAsync(Func<Task> action) {
|
||||
var threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
if (!_reentrantCounts.ContainsKey(threadId)) {
|
||||
_reentrantCounts[threadId] = 0;
|
||||
}
|
||||
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
await _semaphore.WaitAsync();
|
||||
}
|
||||
|
||||
_reentrantCounts[threadId]++;
|
||||
try {
|
||||
await action();
|
||||
}
|
||||
finally {
|
||||
_reentrantCounts[threadId]--;
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteWithLockAsync<T>(Func<T> action) {
|
||||
var threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
if (!_reentrantCounts.ContainsKey(threadId)) {
|
||||
_reentrantCounts[threadId] = 0;
|
||||
}
|
||||
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
await _semaphore.WaitAsync();
|
||||
}
|
||||
|
||||
_reentrantCounts[threadId]++;
|
||||
try {
|
||||
return await Task.Run(action);
|
||||
}
|
||||
finally {
|
||||
_reentrantCounts[threadId]--;
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteWithLockAsync(Action action) {
|
||||
var threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
if (!_reentrantCounts.ContainsKey(threadId)) {
|
||||
_reentrantCounts[threadId] = 0;
|
||||
}
|
||||
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
await _semaphore.WaitAsync();
|
||||
}
|
||||
|
||||
_reentrantCounts[threadId]++;
|
||||
try {
|
||||
await Task.Run(action);
|
||||
}
|
||||
finally {
|
||||
_reentrantCounts[threadId]--;
|
||||
if (_reentrantCounts[threadId] == 0) {
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_semaphore?.Dispose();
|
||||
_semaphore.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ using DomainResults.Common;
|
||||
|
||||
using MaksIT.LetsEncryptServer.Services;
|
||||
using Models.LetsEncryptServer.CertsFlow.Requests;
|
||||
using Models.LetsEncryptServer.Cache.Responses;
|
||||
using MaksIT.LetsEncrypt.Entities;
|
||||
|
||||
namespace MaksIT.LetsEncryptServer.BackgroundServices {
|
||||
public class AutoRenewal : BackgroundService {
|
||||
@ -30,26 +32,23 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
|
||||
while (!stoppingToken.IsCancellationRequested) {
|
||||
_logger.LogInformation("Background service is running.");
|
||||
|
||||
var (accountsResponse, getAccountIdsResult) = await _cacheService.GetAccountsAsync();
|
||||
var (accountsResponse, getAccountIdsResult) = await _cacheService.LoadAccountsFromCacheAsync();
|
||||
if (!getAccountIdsResult.IsSuccess || accountsResponse == null) {
|
||||
LogErrors(getAccountIdsResult.Errors);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var accountId in accountsResponse.AccountIds) {
|
||||
await ProcessAccountAsync(accountId);
|
||||
foreach (var account in accountsResponse) {
|
||||
await ProcessAccountAsync(account);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IDomainResult> ProcessAccountAsync(Guid accountId) {
|
||||
var (cache, loadResult) = await _cacheService.LoadFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
LogErrors(loadResult.Errors);
|
||||
return loadResult;
|
||||
}
|
||||
private async Task<IDomainResult> ProcessAccountAsync(RegistrationCache cache) {
|
||||
|
||||
|
||||
|
||||
var hostnames = cache.GetHostsWithUpcomingSslExpiry();
|
||||
if (hostnames == null) {
|
||||
@ -63,11 +62,11 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
var renewResult = await RenewCertificatesForHostnames(accountId, cache.Contacts, hostnames);
|
||||
var renewResult = await RenewCertificatesForHostnames(cache.AccountId, cache.Contacts, hostnames);
|
||||
if (!renewResult.IsSuccess)
|
||||
return renewResult;
|
||||
|
||||
_logger.LogInformation($"Certificates renewed for account {accountId}");
|
||||
_logger.LogInformation($"Certificates renewed for account {cache.AccountId}");
|
||||
|
||||
return IDomainResult.Success();
|
||||
}
|
||||
|
||||
@ -11,15 +11,12 @@ namespace MaksIT.LetsEncryptServer.Controllers;
|
||||
[ApiController]
|
||||
[Route("api/cache")]
|
||||
public class CacheController : ControllerBase {
|
||||
private readonly Configuration _appSettings;
|
||||
private readonly ICacheRestService _cacheService;
|
||||
|
||||
public CacheController(
|
||||
IOptions<Configuration> appSettings,
|
||||
ICacheService cacheService
|
||||
) {
|
||||
_appSettings = appSettings.Value;
|
||||
_cacheService = (ICacheRestService)cacheService;
|
||||
_cacheService = cacheService;
|
||||
}
|
||||
|
||||
[HttpGet("accounts")]
|
||||
|
||||
@ -10,8 +10,9 @@ using Models.LetsEncryptServer.Cache.Responses;
|
||||
|
||||
namespace MaksIT.LetsEncryptServer.Services;
|
||||
|
||||
public interface ICacheService {
|
||||
Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId);
|
||||
public interface ICacheInternalsService {
|
||||
Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync();
|
||||
Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId);
|
||||
Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache);
|
||||
Task<IDomainResult> DeleteFromCacheAsync(Guid accountId);
|
||||
}
|
||||
@ -28,7 +29,9 @@ public interface ICacheRestService {
|
||||
Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId);
|
||||
}
|
||||
|
||||
public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
public interface ICacheService : ICacheInternalsService, ICacheRestService {}
|
||||
|
||||
public class CacheService : ICacheService, IDisposable {
|
||||
private readonly ILogger<CacheService> _logger;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly LockManager _lockManager;
|
||||
@ -36,7 +39,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
public CacheService(ILogger<CacheService> logger) {
|
||||
_logger = logger;
|
||||
_cacheDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache");
|
||||
_lockManager = new LockManager(1, 1);
|
||||
_lockManager = new LockManager();
|
||||
|
||||
if (!Directory.Exists(_cacheDirectory)) {
|
||||
Directory.CreateDirectory(_cacheDirectory);
|
||||
@ -50,9 +53,39 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
return Path.Combine(_cacheDirectory, $"{accountId}.json");
|
||||
}
|
||||
|
||||
private Guid[] GetCachedAccounts() {
|
||||
return GetCacheFilesPaths().Select(x => Path.GetFileNameWithoutExtension(x).ToGuid()).Where(x => x != Guid.Empty).ToArray();
|
||||
}
|
||||
|
||||
private string[] GetCacheFilesPaths() {
|
||||
return Directory.GetFiles(_cacheDirectory);
|
||||
}
|
||||
|
||||
#region Cache Operations
|
||||
|
||||
public Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId) {
|
||||
public async Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync() {
|
||||
return await _lockManager.ExecuteWithLockAsync(async () => {
|
||||
var accountIds = GetCachedAccounts();
|
||||
var cacheLoadTasks = accountIds.Select(accountId => LoadFromCacheInternalAsync(accountId)).ToList();
|
||||
|
||||
var caches = new List<RegistrationCache>();
|
||||
foreach (var task in cacheLoadTasks) {
|
||||
var (registrationCache, getRegistrationCacheResult) = await task;
|
||||
if (!getRegistrationCacheResult.IsSuccess || registrationCache == null) {
|
||||
// Depending on how you want to handle partial failures, you might want to return here
|
||||
// or continue loading other caches. For now, let's continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
caches.Add(registrationCache);
|
||||
}
|
||||
|
||||
return IDomainResult.Success(caches.ToArray());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId) {
|
||||
return _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId));
|
||||
}
|
||||
|
||||
@ -110,8 +143,8 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
|
||||
public async Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync() {
|
||||
return await _lockManager.ExecuteWithLockAsync(async () => {
|
||||
var cacheFiles = Directory.GetFiles(_cacheDirectory);
|
||||
var accountIds = cacheFiles.Select(x => Path.GetFileNameWithoutExtension(x).ToGuid()).ToArray();
|
||||
|
||||
var accountIds = GetCachedAccounts();
|
||||
var accounts = new List<GetAccountResponse>();
|
||||
|
||||
foreach (var accountId in accountIds) {
|
||||
@ -128,7 +161,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
|
||||
public async Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId) {
|
||||
return await _lockManager.ExecuteWithLockAsync(async () => {
|
||||
var (cache, result) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, result) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!result.IsSuccess || cache == null) {
|
||||
return (null, result);
|
||||
}
|
||||
@ -145,7 +178,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
}
|
||||
|
||||
public async Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return (null, loadResult);
|
||||
}
|
||||
@ -162,7 +195,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
}
|
||||
|
||||
public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return (null, loadResult);
|
||||
}
|
||||
@ -209,7 +242,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
#region Contacts Operations
|
||||
|
||||
public async Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return (null, loadResult);
|
||||
}
|
||||
@ -220,7 +253,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
}
|
||||
|
||||
public async Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return (null, loadResult);
|
||||
}
|
||||
@ -235,7 +268,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
}
|
||||
|
||||
public async Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return (null, loadResult);
|
||||
}
|
||||
@ -274,7 +307,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> DeleteContactAsync(Guid accountId, int index) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return loadResult;
|
||||
}
|
||||
@ -299,7 +332,7 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
|
||||
#region Hostnames Operations
|
||||
|
||||
public async Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId) {
|
||||
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
|
||||
var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache?.CachedCerts == null) {
|
||||
return (null, loadResult);
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ public class CertsFlowService : ICertsFlowService {
|
||||
accountId = Guid.NewGuid();
|
||||
}
|
||||
else {
|
||||
var (loadedCache, loadCaceResutl) = await _cacheService.LoadFromCacheAsync(accountId.Value);
|
||||
var (loadedCache, loadCaceResutl) = await _cacheService.LoadAccountFromCacheAsync(accountId.Value);
|
||||
if (!loadCaceResutl.IsSuccess || loadCaceResutl == null) {
|
||||
accountId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user