settings added save reset feedback

This commit is contained in:
Oliver Kieselbach
2026-03-20 16:03:24 +01:00
parent b745d72089
commit 49204b1290
9 changed files with 98 additions and 196 deletions
@@ -1,6 +1,6 @@
"use client";
import { useState, useCallback } from "react";
import SaveResetBar from "./SaveResetBar";
interface AgentAnalyzersSectionProps {
enableLocalAdminAnalyzer: boolean;
@@ -30,74 +30,6 @@ const BUILTIN_ACCOUNTS = [
"All Users",
];
function SaveResetBar({ onSave, onReset, saving }: { onSave: () => Promise<void> | void; onReset: () => void; saving: boolean }) {
const [saveResult, setSaveResult] = useState<"idle" | "saved" | "error">("idle");
const handleSave = useCallback(async () => {
setSaveResult("idle");
try {
await onSave();
setSaveResult("saved");
setTimeout(() => setSaveResult("idle"), 3000);
} catch {
setSaveResult("error");
setTimeout(() => setSaveResult("idle"), 4000);
}
}, [onSave]);
return (
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
{/* Inline feedback */}
{saveResult === "saved" && (
<span className="flex items-center text-sm text-emerald-600 font-medium animate-fade-in">
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
Saved
</span>
)}
{saveResult === "error" && (
<span className="flex items-center text-sm text-red-600 font-medium">
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
</svg>
Save failed
</span>
)}
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={handleSave}
disabled={saving}
className={`px-4 py-2 rounded-md text-sm text-white font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center space-x-2 ${
saveResult === "saved" ? "bg-emerald-600" : "bg-indigo-600 hover:bg-indigo-700"
}`}
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : saveResult === "saved" ? (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
<span>Saved!</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
);
}
export default function AgentAnalyzersSection({
enableLocalAdminAnalyzer,
setEnableLocalAdminAnalyzer,
@@ -1,5 +1,7 @@
"use client";
import SaveResetBar from "./SaveResetBar";
interface AgentSettingsSectionProps {
enablePerformanceCollector: boolean;
setEnablePerformanceCollector: (value: boolean) => void;
@@ -31,7 +33,7 @@ interface AgentSettingsSectionProps {
setEnrollmentSummaryBrandingImageUrl: (value: string) => void;
enrollmentSummaryLaunchRetrySeconds: number;
setEnrollmentSummaryLaunchRetrySeconds: (value: number) => void;
onSave: () => void;
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
}
@@ -349,30 +351,7 @@ export default function AgentSettingsSection({
</>
)}
{/* Save / Reset */}
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={onSave}
disabled={saving}
className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center space-x-2"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
<SaveResetBar onSave={onSave} onReset={onReset} saving={saving} />
</div>
</div>
</>
@@ -1,12 +1,14 @@
"use client";
import SaveResetBar from "./SaveResetBar";
interface DataManagementSectionProps {
dataRetentionDays: number;
setDataRetentionDays: (value: number) => void;
sessionTimeoutHours: number;
setSessionTimeoutHours: (value: number) => void;
isGalacticAdmin?: boolean;
onSave: () => void;
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
}
@@ -97,30 +99,7 @@ export default function DataManagementSection({
</label>
</div>
{/* Save / Reset */}
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={onSave}
disabled={saving}
className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center space-x-2"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
<SaveResetBar onSave={onSave} onReset={onReset} saving={saving} />
</div>
</div>
);
@@ -4,6 +4,7 @@ import { useMemo } from "react";
import { DiagnosticsLogPath } from "../types";
import { validateDiagnosticsPath } from "@/lib/guardValidation";
import { ValidationIndicator } from "@/components/ValidationIndicator";
import SaveResetBar from "./SaveResetBar";
/** Parses the expiry date from the `se=` parameter of a SAS URL. Returns null if not found. */
export function parseSasExpiry(sasUrl: string): Date | null {
@@ -33,7 +34,7 @@ interface DiagnosticsSectionProps {
newDiagDesc: string;
setNewDiagDesc: (value: string) => void;
unrestrictedMode?: boolean;
onSave: () => void;
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
}
@@ -255,30 +256,7 @@ export default function DiagnosticsSection({
</div>
</div>
{/* Save / Reset */}
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={onSave}
disabled={saving}
className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center space-x-2"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
<SaveResetBar onSave={onSave} onReset={onReset} saving={saving} />
</div>
</div>
);
@@ -1,13 +1,14 @@
"use client";
import { useState } from "react";
import SaveResetBar from "./SaveResetBar";
interface HardwareWhitelistSectionProps {
manufacturerWhitelist: string;
setManufacturerWhitelist: (value: string) => void;
modelWhitelist: string;
setModelWhitelist: (value: string) => void;
onSave: () => void;
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
}
@@ -180,30 +181,7 @@ export default function HardwareWhitelistSection({
setItems={(items) => setModelWhitelist(joinList(items))}
/>
{/* Save / Reset */}
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={onSave}
disabled={saving}
className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center space-x-2"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
<SaveResetBar onSave={onSave} onReset={onReset} saving={saving} />
</div>
</div>
);
@@ -1,5 +1,7 @@
"use client";
import SaveResetBar from "./SaveResetBar";
interface NotificationsSectionProps {
webhookProviderType: number;
setWebhookProviderType: (v: number) => void;
@@ -12,7 +14,7 @@ interface NotificationsSectionProps {
onTestWebhook: () => Promise<void>;
testingWebhook: boolean;
testWebhookResult: { success: boolean; message: string } | null;
onSave: () => void;
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
}
@@ -192,30 +194,7 @@ export default function NotificationsSection({
</div>
)}
{/* Save / Reset */}
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={onSave}
disabled={saving}
className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center space-x-2"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
<SaveResetBar onSave={onSave} onReset={onReset} saving={saving} />
</div>
</div>
);
@@ -0,0 +1,77 @@
"use client";
import { useState, useCallback } from "react";
interface SaveResetBarProps {
onSave: () => Promise<void> | void;
onReset: () => void;
saving: boolean;
}
export default function SaveResetBar({ onSave, onReset, saving }: SaveResetBarProps) {
const [saveResult, setSaveResult] = useState<"idle" | "saved" | "error">("idle");
const handleSave = useCallback(async () => {
setSaveResult("idle");
try {
await onSave();
setSaveResult("saved");
setTimeout(() => setSaveResult("idle"), 3000);
} catch {
setSaveResult("error");
setTimeout(() => setSaveResult("idle"), 4000);
}
}, [onSave]);
return (
<div className="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
{/* Inline feedback */}
{saveResult === "saved" && (
<span className="flex items-center text-sm text-emerald-600 font-medium">
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
Saved
</span>
)}
{saveResult === "error" && (
<span className="flex items-center text-sm text-red-600 font-medium">
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
</svg>
Save failed
</span>
)}
<button
onClick={onReset}
disabled={saving}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm"
>
Reset
</button>
<button
onClick={handleSave}
disabled={saving}
className={`px-4 py-2 rounded-md text-sm text-white font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center space-x-2 ${
saveResult === "saved" ? "bg-emerald-600" : "bg-indigo-600 hover:bg-indigo-700"
}`}
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Saving...</span>
</>
) : saveResult === "saved" ? (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
<span>Saved!</span>
</>
) : (
<span>Save</span>
)}
</button>
</div>
);
}
@@ -5,7 +5,7 @@ import { useState } from "react";
interface UnrestrictedModeSectionProps {
unrestrictedMode: boolean;
setUnrestrictedMode: (value: boolean) => void;
onSave: () => void;
onSave: () => Promise<void> | void;
saving: boolean;
}
File diff suppressed because one or more lines are too long