131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
import { Head } from '@inertiajs/react'
|
|
import { useState } from 'react'
|
|
import SettingsLayout from '~/layouts/SettingsLayout'
|
|
import StyledButton from '~/components/StyledButton'
|
|
import StyledSectionHeader from '~/components/StyledSectionHeader'
|
|
import Alert from '~/components/Alert'
|
|
import Input from '~/components/inputs/Input'
|
|
import { useNotifications } from '~/context/NotificationContext'
|
|
import { useMutation } from '@tanstack/react-query'
|
|
import api from '~/lib/api'
|
|
|
|
export default function AdvancedPage(props: {
|
|
advanced: {
|
|
internetStatusTestUrl: string
|
|
internetStatusTestUrlEnvOverride: boolean
|
|
}
|
|
}) {
|
|
const { addNotification } = useNotifications()
|
|
const { internetStatusTestUrlEnvOverride } = props.advanced
|
|
|
|
const [internetStatusTestUrl, setInternetStatusTestUrl] = useState(
|
|
props.advanced.internetStatusTestUrl ?? ''
|
|
)
|
|
const [testUrlError, setTestUrlError] = useState<string | null>(null)
|
|
|
|
// Mirror the backend validation (admin/app/validators/settings.ts) for instant
|
|
// feedback. The backend remains the source of truth and returns 422 on failure.
|
|
function validateTestUrl(value: string): string | null {
|
|
if (value.trim() === '') return null // empty clears the setting
|
|
try {
|
|
const url = new URL(value)
|
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
return 'Test URL must use http or https.'
|
|
}
|
|
} catch {
|
|
return 'Test URL must be a valid URL (e.g. "https://example.com").'
|
|
}
|
|
return null
|
|
}
|
|
|
|
const updateTestUrlMutation = useMutation({
|
|
mutationFn: async (value: string) => {
|
|
return await api.updateSetting('system.internetStatusTestUrl', value)
|
|
},
|
|
onSuccess: () => {
|
|
addNotification({ message: 'Setting updated successfully.', type: 'success' })
|
|
},
|
|
onError: (error: any) => {
|
|
const msg =
|
|
error?.response?.data?.message ||
|
|
error?.message ||
|
|
'There was an error updating the setting. Please try again.'
|
|
setTestUrlError(msg)
|
|
addNotification({ message: msg, type: 'error' })
|
|
},
|
|
})
|
|
|
|
function handleSaveTestUrl() {
|
|
const trimmed = internetStatusTestUrl.trim()
|
|
const validationError = validateTestUrl(trimmed)
|
|
if (validationError) {
|
|
setTestUrlError(validationError)
|
|
return
|
|
}
|
|
setTestUrlError(null)
|
|
updateTestUrlMutation.mutate(trimmed)
|
|
}
|
|
|
|
return (
|
|
<SettingsLayout>
|
|
<Head title="Advanced Settings | Project N.O.M.A.D." />
|
|
<div className="xl:pl-72 w-full">
|
|
<main className="px-12 py-6">
|
|
<h1 className="text-4xl font-semibold mb-4">Advanced</h1>
|
|
<p className="text-text-muted mb-4">
|
|
Advanced configuration for operators. These settings are optional — the defaults work
|
|
for most deployments.
|
|
</p>
|
|
|
|
<StyledSectionHeader title="Connectivity" className="mt-8 mb-4" />
|
|
<div className="bg-surface-primary rounded-lg border-2 border-border-subtle p-6">
|
|
<p className="text-sm text-text-secondary mb-4">
|
|
N.O.M.A.D. periodically checks whether it can reach the internet. By default it probes
|
|
Cloudflare's utility endpoint with a few fallbacks. Set a custom endpoint below if your
|
|
network blocks the defaults. Leave blank to use the built-in defaults.
|
|
</p>
|
|
|
|
{internetStatusTestUrlEnvOverride && (
|
|
<Alert
|
|
type="info"
|
|
variant="bordered"
|
|
title="Managed by environment variable"
|
|
message="The INTERNET_STATUS_TEST_URL environment variable is set and takes precedence over this setting. Remove it to manage the test URL here."
|
|
className="!mb-4"
|
|
/>
|
|
)}
|
|
|
|
<div className="flex items-end gap-3">
|
|
<div className="flex-1">
|
|
<Input
|
|
name="internetStatusTestUrl"
|
|
label="Internet Status Test URL"
|
|
helpText="A single http(s) URL used to check connectivity. Any HTTP response counts as online."
|
|
placeholder="https://1.1.1.1/cdn-cgi/trace"
|
|
value={internetStatusTestUrl}
|
|
disabled={internetStatusTestUrlEnvOverride}
|
|
error={Boolean(testUrlError)}
|
|
onChange={(e) => {
|
|
setInternetStatusTestUrl(e.target.value)
|
|
setTestUrlError(null)
|
|
}}
|
|
/>
|
|
{testUrlError && <p className="text-sm text-red-600 mt-1">{testUrlError}</p>}
|
|
</div>
|
|
<StyledButton
|
|
variant="primary"
|
|
onClick={handleSaveTestUrl}
|
|
loading={updateTestUrlMutation.isPending}
|
|
disabled={updateTestUrlMutation.isPending || internetStatusTestUrlEnvOverride}
|
|
className="mb-0.5"
|
|
>
|
|
Save
|
|
</StyledButton>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</SettingsLayout>
|
|
)
|
|
}
|