fix: PR suggestions

This commit is contained in:
tvories 2025-12-30 15:07:22 -07:00 committed by Nico
parent 1a12436195
commit 08a19ed38d
4 changed files with 25 additions and 119 deletions

View file

@ -72,7 +72,6 @@ export const CreateRepositoryForm = ({
const form = useForm<RepositoryFormValues>({ const form = useForm<RepositoryFormValues>({
resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema), resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
defaultValues: initialValues, defaultValues: initialValues,
mode: "onTouched",
resetOptions: { resetOptions: {
keepDefaultValues: true, keepDefaultValues: true,
keepDirtyValues: false, keepDirtyValues: false,

View file

@ -1,4 +1,3 @@
import { useState, useRef } from "react";
import type { UseFormReturn } from "react-hook-form"; import type { UseFormReturn } from "react-hook-form";
import { import {
FormControl, FormControl,
@ -22,10 +21,6 @@ type Props = {
export const RestRepositoryForm = ({ form }: Props) => { export const RestRepositoryForm = ({ form }: Props) => {
const insecureTls = form.watch("insecureTls"); const insecureTls = form.watch("insecureTls");
const cacert = form.watch("cacert"); const cacert = form.watch("cacert");
const [showCertTooltip, setShowCertTooltip] = useState(false);
const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
const tooltipTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const tooltipHideTimeoutRef = useRef<NodeJS.Timeout | null>(null);
return ( return (
<> <>
@ -91,81 +86,33 @@ export const RestRepositoryForm = ({ form }: Props) => {
<FormItem> <FormItem>
<FormLabel>CA Certificate (Optional)</FormLabel> <FormLabel>CA Certificate (Optional)</FormLabel>
<FormControl> <FormControl>
<div className="relative"> <Tooltip delayDuration={500}>
<Textarea <TooltipTrigger asChild>
placeholder="-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----" <div>
rows={6} <Textarea
disabled={insecureTls} placeholder="-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----"
{...field} rows={6}
value={insecureTls ? "" : field.value} disabled={insecureTls}
onChange={(e) => { {...field}
field.onChange(e); value={insecureTls ? "" : field.value}
if (e.target.value) { onChange={(e) => {
form.setValue("insecureTls", false); field.onChange(e);
} if (e.target.value) {
}} form.setValue("insecureTls", false);
onPointerEnter={(e) => { }
if (insecureTls && !showCertTooltip) { }}
const rect = e.currentTarget.getBoundingClientRect(); />
setTooltipPos({ </div>
x: e.clientX - rect.left, </TooltipTrigger>
y: e.clientY - rect.top, {insecureTls && (
}); <TooltipContent>
<p className="max-w-xs">
// Clear any existing timeout
if (tooltipTimeoutRef.current) {
clearTimeout(tooltipTimeoutRef.current);
}
// Set new timeout to show tooltip after 500ms
tooltipTimeoutRef.current = setTimeout(() => {
setShowCertTooltip(true);
}, 500);
}
}}
onPointerMove={(e) => {
if (insecureTls && !showCertTooltip) {
const rect = e.currentTarget.getBoundingClientRect();
setTooltipPos({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
}
}}
onPointerLeave={() => {
// Clear show timeout if still waiting
if (tooltipTimeoutRef.current) {
clearTimeout(tooltipTimeoutRef.current);
tooltipTimeoutRef.current = null;
}
// Clear any existing hide timeout
if (tooltipHideTimeoutRef.current) {
clearTimeout(tooltipHideTimeoutRef.current);
}
// Set timeout to hide tooltip after 500ms
tooltipHideTimeoutRef.current = setTimeout(() => {
setShowCertTooltip(false);
}, 500);
}}
/>
{insecureTls && showCertTooltip && (
<div
className="pointer-events-none absolute z-50 rounded-md bg-foreground px-3 py-1.5 text-xs text-background shadow-md"
style={{
left: `${tooltipPos.x}px`,
top: `${tooltipPos.y - 40}px`,
transform: "translateX(-50%)",
}}
>
<p className="max-w-xs text-balance">
CA certificate is disabled because TLS validation is being skipped. Uncheck "Skip TLS Certificate CA certificate is disabled because TLS validation is being skipped. Uncheck "Skip TLS Certificate
Verification" to provide a custom CA certificate. Verification" to provide a custom CA certificate.
</p> </p>
</div> </TooltipContent>
)} )}
</div> </Tooltip>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
Custom CA certificate for self-signed certificates (PEM format).{" "} Custom CA certificate for self-signed certificates (PEM format).{" "}

View file

@ -62,53 +62,13 @@ export const rcloneRepositoryConfigSchema = type({
path: "string", path: "string",
}).and(baseRepositoryConfigSchema); }).and(baseRepositoryConfigSchema);
const pemCertificateType = type("string").narrow((value, ctx) => {
if (!value || value.trim() === "") {
return true;
}
const trimmed = value.trim();
// Check for BEGIN and END markers
if (!trimmed.includes("-----BEGIN CERTIFICATE-----") || !trimmed.includes("-----END CERTIFICATE-----")) {
return ctx.error("Certificate must be in PEM format with BEGIN and END markers");
}
// Extract content between markers
const beginMarker = "-----BEGIN CERTIFICATE-----";
const endMarker = "-----END CERTIFICATE-----";
const beginIndex = trimmed.indexOf(beginMarker);
const endIndex = trimmed.indexOf(endMarker);
if (beginIndex === -1 || endIndex === -1 || endIndex <= beginIndex) {
return ctx.error("Invalid PEM certificate structure");
}
// Extract base64 content
const base64Content = trimmed.substring(beginIndex + beginMarker.length, endIndex).replace(/\s/g, "");
// Validate base64 format
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
if (!base64Regex.test(base64Content)) {
return ctx.error("Certificate contains invalid base64 characters");
}
if (base64Content.length === 0) {
return ctx.error("Certificate content is empty");
}
return true;
});
const optionalPemCertificate = pemCertificateType.optional();
export const restRepositoryConfigSchema = type({ export const restRepositoryConfigSchema = type({
backend: "'rest'", backend: "'rest'",
url: "string", url: "string",
username: "string?", username: "string?",
password: "string?", password: "string?",
path: "string?", path: "string?",
cacert: optionalPemCertificate as any, cacert: "string?",
insecureTls: "boolean?", insecureTls: "boolean?",
}).and(baseRepositoryConfigSchema); }).and(baseRepositoryConfigSchema);

View file

@ -886,7 +886,7 @@ export const cleanupTemporaryKeys = async (config: RepositoryConfig, env: Record
await fs.unlink(env.GOOGLE_APPLICATION_CREDENTIALS).catch(() => {}); await fs.unlink(env.GOOGLE_APPLICATION_CREDENTIALS).catch(() => {});
} }
if (config.backend === "rest" && env.RESTIC_CACERT) { if (env.RESTIC_CACERT) {
await fs.unlink(env.RESTIC_CACERT).catch(() => {}); await fs.unlink(env.RESTIC_CACERT).catch(() => {});
} }
}; };