Ensure per-row SSL for apex, business.*, and customer.* hosts.
Lock icon probes live TLS and issues only missing certs; toast z-index sits above modals; ssl-sync agent supports blocking wait. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
aeaad0f645
commit
0212148a73
@@ -10,7 +10,6 @@ import {
|
||||
RotateCcw,
|
||||
Search,
|
||||
ShieldPlus,
|
||||
Unlock,
|
||||
} from 'lucide-react'
|
||||
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
|
||||
import { DomainLinksOverflowMenu } from '../components/DomainLinksOverflowMenu'
|
||||
@@ -26,7 +25,6 @@ import {
|
||||
listDomains,
|
||||
removeDomain,
|
||||
setDomainActive,
|
||||
setDomainSsl,
|
||||
syncSsl,
|
||||
updateDomain,
|
||||
} from '../services/domainService'
|
||||
@@ -105,7 +103,6 @@ export function WebsitesPage() {
|
||||
|
||||
const [removeTarget, setRemoveTarget] = useState<DomainListItem | null>(null)
|
||||
const [togglingActiveId, setTogglingActiveId] = useState<number | null>(null)
|
||||
const [togglingSslId, setTogglingSslId] = useState<number | null>(null)
|
||||
const [deployingId, setDeployingId] = useState<DomainListItem['id'] | null>(null)
|
||||
const [syncingSsl, setSyncingSsl] = useState(false)
|
||||
const [issuingWebsiteSsl, setIssuingWebsiteSsl] = useState(false)
|
||||
@@ -236,38 +233,6 @@ export function WebsitesPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleSsl(domain: DomainListItem, sslEnabled: boolean) {
|
||||
setTogglingSslId(domain.id)
|
||||
setError('')
|
||||
setData((prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
items: prev.items.map((item) => (item.id === domain.id ? { ...item, sslEnabled } : item)),
|
||||
}
|
||||
})
|
||||
try {
|
||||
await setDomainSsl(domain.id, sslEnabled)
|
||||
showToast(
|
||||
`SSL ${sslEnabled ? 'enabled' : 'disabled'} for "${domain.host}".`,
|
||||
'success',
|
||||
)
|
||||
} catch (err) {
|
||||
setData((prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
item.id === domain.id ? { ...item, sslEnabled: !sslEnabled } : item,
|
||||
),
|
||||
}
|
||||
})
|
||||
setError(err instanceof ApiError ? err.message : 'Unable to update SSL status.')
|
||||
} finally {
|
||||
setTogglingSslId(null)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSyncSsl() {
|
||||
if (syncingSsl || issuingWebsiteSsl) return
|
||||
|
||||
@@ -309,18 +274,9 @@ export function WebsitesPage() {
|
||||
} else {
|
||||
showToast(result.message || 'Website SSL updated.', 'success')
|
||||
}
|
||||
if (result.issued.length > 0) {
|
||||
setData((prev) => {
|
||||
if (!prev) return prev
|
||||
const issued = new Set(result.issued)
|
||||
return {
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
issued.has(item.host) ? { ...item, sslEnabled: true } : item,
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
// Re-load so ssl_enabled matches live probes (including corrected false flags).
|
||||
const refreshed = await listDomains({ page, pageSize: PAGE_SIZE, ...appliedFilters })
|
||||
setData(refreshed)
|
||||
} catch (err) {
|
||||
const message = err instanceof ApiError ? err.message : 'Unable to issue website SSL.'
|
||||
setError(message)
|
||||
@@ -331,13 +287,16 @@ export function WebsitesPage() {
|
||||
}
|
||||
|
||||
async function handleIssueDomainSsl(domain: DomainListItem) {
|
||||
if (!domain.deploySlug || issuingSslId != null) return
|
||||
if (issuingSslId != null) return
|
||||
|
||||
flushSync(() => {
|
||||
setIssuingSslId(domain.id)
|
||||
})
|
||||
setError('')
|
||||
showToast(`Issuing SSL for "${domain.host}"…`, 'info')
|
||||
showToast(
|
||||
`Checking SSL for ${domain.host}, business.${domain.host}, customer.${domain.host}…`,
|
||||
'info',
|
||||
)
|
||||
|
||||
try {
|
||||
const result = await issueDomainSsl(domain.id)
|
||||
@@ -350,9 +309,15 @@ export function WebsitesPage() {
|
||||
),
|
||||
}
|
||||
})
|
||||
showToast(result.message || `SSL issued for "${domain.host}".`, 'success')
|
||||
if (result.failed.length > 0) {
|
||||
const detail = result.failed.map((f) => `${f.host}: ${f.error}`).join(' · ')
|
||||
setError(detail)
|
||||
showToast(result.message, result.issued.length > 0 ? 'info' : 'error')
|
||||
} else {
|
||||
showToast(result.message || `SSL ok for "${domain.host}".`, 'success')
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof ApiError ? err.message : 'Unable to issue SSL.'
|
||||
const message = err instanceof ApiError ? err.message : 'Unable to ensure SSL.'
|
||||
setError(message)
|
||||
showToast(message, 'error')
|
||||
} finally {
|
||||
@@ -468,7 +433,7 @@ export function WebsitesPage() {
|
||||
onClick={() => void handleIssueWebsiteSsl()}
|
||||
disabled={issuingWebsiteSsl || syncingSsl}
|
||||
aria-busy={issuingWebsiteSsl}
|
||||
title="Issue Let's Encrypt certs for storefront domains missing SSL"
|
||||
title="Probe storefront HTTPS and issue Let's Encrypt where the cert is missing or wrong"
|
||||
>
|
||||
{issuingWebsiteSsl ? (
|
||||
<Loader2 size={16} className={styles.spin} aria-hidden />
|
||||
@@ -665,46 +630,31 @@ export function WebsitesPage() {
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
{domain.deploySlug && !domain.sslEnabled ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`${tableStyles.controlBtn} ${
|
||||
issuingSslId === domain.id ? styles.deployBusy : ''
|
||||
}`}
|
||||
onClick={() => void handleIssueDomainSsl(domain)}
|
||||
disabled={issuingSslId === domain.id || issuingWebsiteSsl}
|
||||
title={
|
||||
issuingSslId === domain.id
|
||||
? 'Issuing SSL…'
|
||||
: 'Issue SSL certificate'
|
||||
}
|
||||
aria-label={
|
||||
issuingSslId === domain.id
|
||||
? 'Issuing SSL'
|
||||
: 'Issue SSL certificate'
|
||||
}
|
||||
aria-busy={issuingSslId === domain.id}
|
||||
>
|
||||
{issuingSslId === domain.id ? (
|
||||
<Loader2 size={16} className={styles.spin} aria-hidden />
|
||||
) : (
|
||||
<ShieldPlus size={16} />
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={tableStyles.controlBtn}
|
||||
onClick={() => void handleToggleSsl(domain, !domain.sslEnabled)}
|
||||
disabled={togglingSslId === domain.id}
|
||||
title={domain.sslEnabled ? 'Mark SSL invalid' : 'Mark SSL valid'}
|
||||
aria-label={
|
||||
domain.sslEnabled ? 'Mark SSL invalid' : 'Mark SSL valid'
|
||||
}
|
||||
>
|
||||
{domain.sslEnabled ? <Lock size={16} /> : <Unlock size={16} />}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={`${tableStyles.controlBtn} ${
|
||||
issuingSslId === domain.id ? styles.deployBusy : ''
|
||||
}`}
|
||||
onClick={() => void handleIssueDomainSsl(domain)}
|
||||
disabled={issuingSslId === domain.id || issuingWebsiteSsl || syncingSsl}
|
||||
title={
|
||||
issuingSslId === domain.id
|
||||
? 'Checking / issuing SSL…'
|
||||
: 'Check & issue SSL for domain, business.*, customer.*'
|
||||
}
|
||||
aria-label={
|
||||
issuingSslId === domain.id
|
||||
? 'Ensuring SSL'
|
||||
: 'Check and issue SSL'
|
||||
}
|
||||
aria-busy={issuingSslId === domain.id}
|
||||
>
|
||||
{issuingSslId === domain.id ? (
|
||||
<Loader2 size={16} className={styles.spin} aria-hidden />
|
||||
) : (
|
||||
<Lock size={16} />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={tableStyles.controlBtn}
|
||||
|
||||
Reference in New Issue
Block a user