homeio
PublicForked from doctor-io/homeio
useSettingsBackend.ts
"use client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo, useRef } from "react";
import { toast } from "sonner";
import { dispatchDesktopNotificationEvent } from "@/lib/desktop/notification-events";
import {
SYSTEM_SECURITY_BAN_DURATION_MAX,
SYSTEM_SECURITY_BAN_DURATION_MIN,
SYSTEM_SECURITY_MAX_RETRIES_MAX,
SYSTEM_SECURITY_MAX_RETRIES_MIN,
SYSTEM_SECURITY_POLICY_OPTIONS,
SYSTEM_TIMEZONE_OPTIONS,
type SystemBackupListResponse,
type SystemBackupSettings,
type SystemPreferences,
type SystemSecuritySettings,
type SystemUpdateStatus,
} from "@/lib/shared/contracts/system";
import { queryKeys } from "@/lib/shared/query-keys";
import { useCurrentUser } from "@/hooks/useCurrentUser";
import {
applySystemUpdateRequest,
checkSystemUpdatesRequest,
factoryResetRequest,
fetchPowerCapabilitiesRequest,
fetchScheduledRebootRequest,
fetchSystemBackupsRequest,
fetchSystemPreferencesRequest,
fetchSystemSecurityRequest,
fetchSystemUpdatesRequest,
pruneDockerImagesRequest,
pruneDockerVolumesRequest,
rebootNowRequest,
restoreBackupRequest,
runBackupNowRequest,
saveBackupSettingsRequest,
saveScheduledRebootRequest,
saveSystemPreferencesRequest,
saveSystemSecurityRequest,
shutdownNowRequest,
} from "@/modules/settings/hooks/backend/api";
import {
CHECK_UPDATES_COOLDOWN_MS,
DEFAULT_BACKUP_ROOT,
DEFAULT_SCHEDULED_REBOOT_CONFIG,
} from "@/modules/settings/hooks/backend/constants";
import { createSettingsCapabilities } from "@/modules/settings/hooks/backend/capabilities";
import {
formatBytes,
formatGigabytes,
formatUptime,
mapContainerToViewModel,
} from "@/modules/settings/hooks/backend/formatters";
import type { ScheduledRebootConfig } from "@/modules/settings/hooks/backend/types";
import {
persistPowerActionState,
useUpdateRecoveryState,
} from "@/modules/settings/hooks/useUpdateRecoveryState";
import { useDockerStatsSnapshot } from "@/modules/system/hooks/useDockerStats";
import { useDockerInfo } from "@/modules/system/hooks/useDockerInfo";
import { useLocalFolderShares } from "@/modules/files/hooks/useLocalFolderShares";
import { useNetworkStatus } from "@/modules/system/hooks/useNetworkStatus";
import { useNetworkShares } from "@/modules/files/hooks/useNetworkShares";
import { useSystemMetrics } from "@/modules/system/hooks/useSystemMetrics";
import { useWifiNetworks } from "@/modules/system/hooks/useWifiNetworks";
export function useSettingsBackend() {
const queryClient = useQueryClient();
const { isUpdateRecoveryActive, setIsUpdateRecoveryActive } = useUpdateRecoveryState();
const currentUserQuery = useCurrentUser();
const systemMetricsQuery = useSystemMetrics();
const networkStatusQuery = useNetworkStatus();
const wifiNetworksQuery = useWifiNetworks();
const localFolderSharesQuery = useLocalFolderShares();
const networkSharesQuery = useNetworkShares();
const dockerStatsSnapshot = useDockerStatsSnapshot();
const dockerInfoQuery = useDockerInfo();
const systemUpdatesQuery = useQuery({
queryKey: queryKeys.systemUpdates,
queryFn: fetchSystemUpdatesRequest,
staleTime: 60_000,
enabled: !isUpdateRecoveryActive,
});
const systemPreferencesQuery = useQuery({
queryKey: queryKeys.systemPreferences,
queryFn: fetchSystemPreferencesRequest,
staleTime: 60_000,
});
const systemSecurityQuery = useQuery({
queryKey: queryKeys.systemSecurity,
queryFn: fetchSystemSecurityRequest,
staleTime: 60_000,
});
const backupsQuery = useQuery({
queryKey: queryKeys.systemBackups,
queryFn: fetchSystemBackupsRequest,
staleTime: 60_000,
});
const scheduledRebootQuery = useQuery({
queryKey: queryKeys.powerSchedule,
queryFn: fetchScheduledRebootRequest,
staleTime: 60_000,
});
const powerCapabilitiesQuery = useQuery({
queryKey: queryKeys.powerCapabilities,
queryFn: fetchPowerCapabilitiesRequest,
staleTime: 5 * 60_000,
});
const lastCheckedAtRef = useRef<number | null>(null);
const checkUpdatesMutation = useMutation({
mutationFn: checkSystemUpdatesRequest,
onSuccess: async (data) => {
lastCheckedAtRef.current = Date.now();
queryClient.setQueryData(queryKeys.systemUpdates, data);
await queryClient.invalidateQueries({ queryKey: queryKeys.systemUpdates });
},
});
const applySystemUpdateMutation = useMutation({
mutationFn: applySystemUpdateRequest,
});
const systemPreferencesMutation = useMutation({
mutationFn: saveSystemPreferencesRequest,
onSuccess: async (data) => {
queryClient.setQueryData(queryKeys.systemPreferences, data);
await Promise.all([
queryClient.invalidateQueries({ queryKey: queryKeys.systemPreferences }),
queryClient.invalidateQueries({ queryKey: queryKeys.systemMetrics }),
]);
},
});
const systemSecurityMutation = useMutation({
mutationFn: saveSystemSecurityRequest,
onSuccess: async (data) => {
queryClient.setQueryData(queryKeys.systemSecurity, data);
await queryClient.invalidateQueries({ queryKey: queryKeys.systemSecurity });
},
});
const rebootMutation = useMutation({
mutationFn: rebootNowRequest,
onSuccess: () => {
persistPowerActionState("reboot");
},
});
const shutdownMutation = useMutation({
mutationFn: shutdownNowRequest,
onSuccess: () => {
persistPowerActionState("shutdown");
},
});
const factoryResetMutation = useMutation({
mutationFn: factoryResetRequest,
onSuccess: () => {
persistPowerActionState("factory-reset");
},
});
const scheduledRebootMutation = useMutation({
mutationFn: saveScheduledRebootRequest,
onSuccess: async (data) => {
queryClient.setQueryData(queryKeys.powerSchedule, data);
await queryClient.invalidateQueries({ queryKey: queryKeys.powerSchedule });
},
});
const pruneImagesMutation = useMutation({
mutationFn: pruneDockerImagesRequest,
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: queryKeys.dockerInfo }),
queryClient.invalidateQueries({ queryKey: queryKeys.systemMetrics }),
]);
},
});
const pruneVolumesMutation = useMutation({
mutationFn: pruneDockerVolumesRequest,
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: queryKeys.dockerInfo }),
queryClient.invalidateQueries({ queryKey: queryKeys.systemMetrics }),
]);
},
});
const backupSettingsMutation = useMutation({
mutationFn: saveBackupSettingsRequest,
onSuccess: async (data) => {
queryClient.setQueryData<SystemBackupListResponse | undefined>(
queryKeys.systemBackups,
(current) =>
current
? {
...current,
settings: data,
}
: {
settings: data,
backups: [],
backupRoot: DEFAULT_BACKUP_ROOT,
},
);
await queryClient.invalidateQueries({ queryKey: queryKeys.systemBackups });
},
});
const backupRunMutation = useMutation({
mutationFn: runBackupNowRequest,
onSuccess: async (data) => {
dispatchDesktopNotificationEvent({
id: `backup-run-${data.backup.id}`,
title: "Backup Completed",
message: `Backup ${data.backup.id} finished successfully.`,
kind: "backup-report",
});
await queryClient.invalidateQueries({ queryKey: queryKeys.systemBackups });
},
onError: (error) => {
dispatchDesktopNotificationEvent({
id: `backup-run-error-${Date.now()}`,
title: "Backup Failed",
message: error instanceof Error ? error.message : "Backup run failed.",
kind: "backup-report",
});
},
});
const restoreBackupMutation = useMutation({
mutationFn: restoreBackupRequest,
onSuccess: (_data, backupId) => {
dispatchDesktopNotificationEvent({
id: `backup-restore-${backupId}`,
title: "Restore Scheduled",
message: `Restore from backup ${backupId} was accepted. Homeio will restart to finish it.`,
kind: "backup-report",
});
persistPowerActionState("restore");
},
onError: (error, backupId) => {
dispatchDesktopNotificationEvent({
id: `backup-restore-error-${backupId}-${Date.now()}`,
title: "Restore Failed",
message: error instanceof Error ? error.message : `Restore from ${backupId} failed.`,
kind: "backup-report",
});
},
});
async function checkForUpdates() {
if (checkUpdatesMutation.isPending || isUpdateRecoveryActive) return;
const lastChecked = lastCheckedAtRef.current;
if (lastChecked !== null && Date.now() - lastChecked < CHECK_UPDATES_COOLDOWN_MS) {
const currentStatus = queryClient.getQueryData<SystemUpdateStatus>(queryKeys.systemUpdates);
if (currentStatus?.updateAvailable && currentStatus.latestVersion) {
toast.success(`Homeio ${currentStatus.latestVersion} is available.`);
} else if (currentStatus?.latestVersion) {
toast.success("Homeio is already up to date.");
} else {
toast.info("Update check ran recently. Please wait a moment before trying again.");
}
return;
}
try {
const data = await checkUpdatesMutation.mutateAsync(undefined);
if (data.updateAvailable && data.latestVersion) {
toast.success(`Homeio ${data.latestVersion} is available.`);
} else {
toast.success("Homeio is already up to date.");
}
return data;
} catch (error) {
toast.error(
error instanceof Error ? error.message : "Failed to check for Homeio updates.",
);
throw error;
}
}
const general = useMemo(() => {
const metrics = systemMetricsQuery.data;
const memory = metrics?.memory;
const systemPreferences = systemPreferencesQuery.data;
return {
hostname: systemPreferences?.hostname ?? metrics?.hostname ?? "--",
platform: metrics?.platform ?? "--",
kernel: metrics?.process.nodeVersion ?? "--",
architecture: metrics?.architecture ?? "--",
uptime: formatUptime(metrics?.uptimeSeconds),
appVersion: process.env.NEXT_PUBLIC_APP_VERSION ?? "--",
username: currentUserQuery.data?.username ?? "--",
twoFactor: currentUserQuery.data?.twoFactor ?? { enabled: false, enrolledAt: null },
isDemoMode: currentUserQuery.data?.isDemoMode ?? false,
cpuSummary:
metrics?.cpu.normalizedPercent !== undefined
? `${metrics.cpu.normalizedPercent.toFixed(0)}% load`
: "--",
memorySummary:
memory && memory.totalBytes > 0
? `${formatGigabytes(memory.usedBytes)} / ${formatGigabytes(memory.totalBytes)}`
: "--",
temperatureSummary:
metrics?.temperature.mainCelsius !== null && metrics?.temperature.mainCelsius !== undefined
? `${metrics.temperature.mainCelsius.toFixed(1)} C`
: "--",
processUptime: formatUptime(metrics?.process.uptimeSeconds),
isLoading:
systemMetricsQuery.isLoading ||
currentUserQuery.isLoading ||
systemPreferencesQuery.isLoading,
unavailable: Boolean(systemMetricsQuery.error) || Boolean(systemPreferencesQuery.error),
warning:
systemMetricsQuery.error instanceof Error
? "System metrics unavailable. Showing placeholders where needed."
: systemPreferencesQuery.error instanceof Error
? "System preferences unavailable."
: currentUserQuery.error instanceof Error
? "User identity unavailable."
: null,
};
}, [
currentUserQuery.data?.username,
currentUserQuery.data?.twoFactor,
currentUserQuery.data?.isDemoMode,
currentUserQuery.error,
currentUserQuery.isLoading,
systemPreferencesQuery.data,
systemPreferencesQuery.error,
systemPreferencesQuery.isLoading,
systemMetricsQuery.data,
systemMetricsQuery.error,
systemMetricsQuery.isLoading,
]);
const network = useMemo(() => {
const status = networkStatusQuery.data;
const networks = wifiNetworksQuery.data ?? [];
return {
connected: status?.connected ?? false,
iface: status?.iface ?? "--",
ipv4: status?.ipv4 ?? "--",
ssid: status?.ssid ?? "--",
signalPercent:
status?.signalPercent !== null && status?.signalPercent !== undefined
? `${status.signalPercent}%`
: "--",
wifiCount: networks.length,
topSsids: networks
.map((networkEntry) => networkEntry.ssid)
.filter((ssid) => ssid.trim().length > 0)
.slice(0, 3),
isLoading: networkStatusQuery.isLoading || wifiNetworksQuery.isLoading,
unavailable: Boolean(networkStatusQuery.error),
warning:
networkStatusQuery.error instanceof Error
? "Network status unavailable."
: wifiNetworksQuery.error instanceof Error
? "Wi-Fi scan unavailable."
: null,
};
}, [
networkStatusQuery.data,
networkStatusQuery.error,
networkStatusQuery.isLoading,
wifiNetworksQuery.data,
wifiNetworksQuery.error,
wifiNetworksQuery.isLoading,
]);
const docker = useMemo(() => {
const containers = dockerStatsSnapshot.stats.map(mapContainerToViewModel);
const info = dockerInfoQuery.data;
return {
containers,
total: containers.length,
running: containers.filter((container) => container.status === "running").length,
images: info ? String(info.images) : "--",
engineVersion: info?.engineVersion ?? "--",
composeVersion: info?.composeVersion ?? "--",
storageDriver: info?.storageDriver ?? "--",
cgroupDriver: info?.cgroupDriver ?? "--",
isLoading: dockerStatsSnapshot.isLoading || dockerInfoQuery.isLoading,
unavailable: Boolean(dockerStatsSnapshot.error),
warning: dockerStatsSnapshot.error
? "Docker stats unavailable."
: dockerInfoQuery.error
? "Docker engine info unavailable."
: null,
pruneImages: {
isPending: pruneImagesMutation.isPending,
error:
pruneImagesMutation.error instanceof Error
? pruneImagesMutation.error.message
: null,
},
pruneVolumes: {
isPending: pruneVolumesMutation.isPending,
error:
pruneVolumesMutation.error instanceof Error
? pruneVolumesMutation.error.message
: null,
},
};
}, [
dockerInfoQuery.data,
dockerInfoQuery.error,
dockerInfoQuery.isLoading,
dockerStatsSnapshot.error,
dockerStatsSnapshot.isLoading,
dockerStatsSnapshot.stats,
pruneImagesMutation.error,
pruneImagesMutation.isPending,
pruneVolumesMutation.error,
pruneVolumesMutation.isPending,
]);
const storage = useMemo(() => {
const rootStorage = systemMetricsQuery.data?.storage;
const disks = (rootStorage?.volumes ?? []).map((volume) => ({
id: volume.id,
label: volume.label,
mountPath: volume.mountPath,
totalBytes: volume.totalBytes,
usedBytes: volume.usedBytes,
usedPercent: volume.usedPercent,
mediaType: volume.mediaType,
filesystem: volume.filesystem,
}));
const localFolderShares = localFolderSharesQuery.data ?? [];
const networkShares = networkSharesQuery.data ?? [];
const shares = [
...localFolderShares.map((share) => ({
id: share.id,
name: share.shareName,
path: `/${share.sharedPath}`,
source: `/${share.sourcePath}`,
protocol: "SMB usershare",
status:
share.isMounted && share.isExported
? "Mounted"
: share.isMounted
? "Partially configured"
: "Unmounted",
})),
...networkShares.map((share) => ({
id: share.id,
name: `${share.host}/${share.share}`,
path: `/${share.mountPath}`,
source: `//${share.host}/${share.share}`,
protocol: "SMB/CIFS",
status: share.isMounted ? "Mounted" : "Unmounted",
})),
].sort((left, right) => left.name.localeCompare(right.name));
const warnings: string[] = [];
if (systemMetricsQuery.error) warnings.push("Storage metrics unavailable.");
if (localFolderSharesQuery.error) {
warnings.push("Local shared folders list unavailable.");
}
if (networkSharesQuery.error) {
warnings.push("Network shares list unavailable.");
}
return {
mountPath: rootStorage?.mountPath ?? "--",
totalBytes: rootStorage?.totalBytes ?? null,
usedBytes: rootStorage?.usedBytes ?? null,
availableBytes: rootStorage?.availableBytes ?? null,
usedPercent:
rootStorage && Number.isFinite(rootStorage.usedPercent)
? Number(rootStorage.usedPercent.toFixed(1))
: null,
summary:
rootStorage && rootStorage.totalBytes > 0
? `${formatBytes(rootStorage.usedBytes)} / ${formatBytes(rootStorage.totalBytes)}`
: "--",
shares,
localShareCount: localFolderShares.length,
networkShareCount: networkShares.length,
disks,
raid: rootStorage?.raid ?? null,
smart: rootStorage?.smart ?? null,
isLoading:
systemMetricsQuery.isLoading ||
localFolderSharesQuery.isLoading ||
networkSharesQuery.isLoading,
unavailable: Boolean(systemMetricsQuery.error),
warning: warnings.length > 0 ? warnings.join(" ") : null,
};
}, [
localFolderSharesQuery.data,
localFolderSharesQuery.error,
localFolderSharesQuery.isLoading,
networkSharesQuery.data,
networkSharesQuery.error,
networkSharesQuery.isLoading,
systemMetricsQuery.data?.storage,
systemMetricsQuery.error,
systemMetricsQuery.isLoading,
]);
const updates = useMemo(() => {
const data = systemUpdatesQuery.data ?? {
currentVersion: general.appVersion,
latestVersion: null,
updateAvailable: false,
checkedAt: null,
};
return {
currentVersion: data.currentVersion,
latestVersion: data.latestVersion,
updateAvailable: data.updateAvailable,
checkedAt: data.checkedAt,
isLoading: systemUpdatesQuery.isLoading,
unavailable: !isUpdateRecoveryActive && Boolean(systemUpdatesQuery.error),
warning:
isUpdateRecoveryActive
? null
: systemUpdatesQuery.error
? "Homeio update status unavailable."
: null,
isChecking: checkUpdatesMutation.isPending,
isApplying: applySystemUpdateMutation.isPending,
checkError:
!isUpdateRecoveryActive && checkUpdatesMutation.error instanceof Error
? checkUpdatesMutation.error.message
: null,
applyError:
!isUpdateRecoveryActive && applySystemUpdateMutation.error instanceof Error
? applySystemUpdateMutation.error.message
: null,
isRecoveryActive: isUpdateRecoveryActive,
};
}, [
applySystemUpdateMutation.error,
applySystemUpdateMutation.isPending,
checkUpdatesMutation.error,
checkUpdatesMutation.isPending,
general.appVersion,
isUpdateRecoveryActive,
systemUpdatesQuery.data,
systemUpdatesQuery.error,
systemUpdatesQuery.isLoading,
]);
const generalPreferences = useMemo(() => {
const data = systemPreferencesQuery.data ?? {
hostname: general.hostname,
timezone: SYSTEM_TIMEZONE_OPTIONS[0],
};
return {
hostname: data.hostname,
timezone: data.timezone,
isLoading: systemPreferencesQuery.isLoading,
isSaving: systemPreferencesMutation.isPending,
error:
systemPreferencesMutation.error instanceof Error
? systemPreferencesMutation.error.message
: systemPreferencesQuery.error instanceof Error
? systemPreferencesQuery.error.message
: null,
timezoneOptions:
data.timezone &&
!SYSTEM_TIMEZONE_OPTIONS.some((option) => option === data.timezone)
? [data.timezone, ...SYSTEM_TIMEZONE_OPTIONS]
: [...SYSTEM_TIMEZONE_OPTIONS],
};
}, [
general.hostname,
systemPreferencesMutation.error,
systemPreferencesMutation.isPending,
systemPreferencesQuery.data,
systemPreferencesQuery.error,
systemPreferencesQuery.isLoading,
]);
const backup = useMemo(() => {
const snapshot = backupsQuery.data;
const settings = snapshot?.settings ?? {
enabled: false,
frequency: "weekly" as const,
dayOfWeek: "sunday" as const,
time: "03:00",
retentionCount: 7,
};
return {
settings: {
...settings,
isLoading: backupsQuery.isLoading,
isSaving: backupSettingsMutation.isPending,
error:
backupSettingsMutation.error instanceof Error
? backupSettingsMutation.error.message
: backupsQuery.error instanceof Error
? backupsQuery.error.message
: null,
},
backups: snapshot?.backups ?? [],
backupRoot: snapshot?.backupRoot ?? DEFAULT_BACKUP_ROOT,
isLoading: backupsQuery.isLoading,
runNow: {
isPending: backupRunMutation.isPending,
error:
backupRunMutation.error instanceof Error
? backupRunMutation.error.message
: null,
},
restore: {
isPending: restoreBackupMutation.isPending,
error:
restoreBackupMutation.error instanceof Error
? restoreBackupMutation.error.message
: null,
},
};
}, [
backupsQuery.data,
backupsQuery.error,
backupsQuery.isLoading,
backupRunMutation.error,
backupRunMutation.isPending,
backupSettingsMutation.error,
backupSettingsMutation.isPending,
restoreBackupMutation.error,
restoreBackupMutation.isPending,
]);
const security = useMemo(() => {
const data = systemSecurityQuery.data ?? {
firewallEnabled: false,
firewallIncomingPolicy: "deny" as const,
firewallOutgoingPolicy: "allow" as const,
fail2banEnabled: false,
fail2banMaxRetries: 5,
fail2banBanDurationSeconds: 3600,
};
return {
firewall: {
enabled: data.firewallEnabled,
incomingPolicy: data.firewallIncomingPolicy,
outgoingPolicy: data.firewallOutgoingPolicy,
policyOptions: [...SYSTEM_SECURITY_POLICY_OPTIONS],
},
fail2ban: {
enabled: data.fail2banEnabled,
maxRetries: data.fail2banMaxRetries,
banDuration: data.fail2banBanDurationSeconds,
maxRetriesBounds: {
min: SYSTEM_SECURITY_MAX_RETRIES_MIN,
max: SYSTEM_SECURITY_MAX_RETRIES_MAX,
},
banDurationBounds: {
min: SYSTEM_SECURITY_BAN_DURATION_MIN,
max: SYSTEM_SECURITY_BAN_DURATION_MAX,
},
},
isLoading: systemSecurityQuery.isLoading,
isSaving: systemSecurityMutation.isPending,
unavailable: Boolean(systemSecurityQuery.error),
error:
systemSecurityMutation.error instanceof Error
? systemSecurityMutation.error.message
: systemSecurityQuery.error instanceof Error
? systemSecurityQuery.error.message
: null,
};
}, [
systemSecurityMutation.error,
systemSecurityMutation.isPending,
systemSecurityQuery.data,
systemSecurityQuery.error,
systemSecurityQuery.isLoading,
]);
const power = useMemo(() => {
const scheduledReboot: ScheduledRebootConfig =
scheduledRebootQuery.data ?? DEFAULT_SCHEDULED_REBOOT_CONFIG;
const caps = powerCapabilitiesQuery.data;
return {
reboot: {
available: caps?.reboot ?? false,
isPending: rebootMutation.isPending,
error:
rebootMutation.error instanceof Error
? rebootMutation.error.message
: null,
},
shutdown: {
available: caps?.shutdown ?? false,
isPending: shutdownMutation.isPending,
error:
shutdownMutation.error instanceof Error
? shutdownMutation.error.message
: null,
},
factoryReset: {
available: caps?.factoryReset ?? false,
isPending: factoryResetMutation.isPending,
error:
factoryResetMutation.error instanceof Error
? factoryResetMutation.error.message
: null,
},
scheduledReboot: {
...scheduledReboot,
available: caps?.scheduledReboot ?? false,
isLoading: scheduledRebootQuery.isLoading,
isPending: scheduledRebootMutation.isPending,
error:
scheduledRebootMutation.error instanceof Error
? scheduledRebootMutation.error.message
: scheduledRebootQuery.error instanceof Error
? scheduledRebootQuery.error.message
: null,
},
};
}, [
factoryResetMutation.error,
factoryResetMutation.isPending,
powerCapabilitiesQuery.data,
rebootMutation.error,
rebootMutation.isPending,
scheduledRebootMutation.error,
scheduledRebootMutation.isPending,
scheduledRebootQuery.data,
scheduledRebootQuery.error,
scheduledRebootQuery.isLoading,
shutdownMutation.error,
shutdownMutation.isPending,
]);
const capabilities = useMemo(() => createSettingsCapabilities(), []);
return {
general,
generalPreferences,
network,
storage,
docker,
security,
backup,
updates,
power,
capabilities,
actions: {
checkForUpdates,
applySystemUpdate: async () => {
if (isUpdateRecoveryActive || applySystemUpdateMutation.isPending) return;
try {
await applySystemUpdateMutation.mutateAsync(undefined);
} catch {
return;
}
setIsUpdateRecoveryActive(true);
persistPowerActionState("update", {
requestDispatchedAt: new Date().toISOString(),
});
window.location.replace("/updating");
},
saveGeneralPreferences: async (input: SystemPreferences) => {
await systemPreferencesMutation.mutateAsync(input);
},
saveSecuritySettings: async (input: SystemSecuritySettings) => {
await systemSecurityMutation.mutateAsync(input);
},
saveBackupSettings: async (input: SystemBackupSettings) => {
await backupSettingsMutation.mutateAsync(input);
},
runBackupNow: async () => {
await backupRunMutation.mutateAsync(undefined);
},
restoreBackup: async (backupId: string) => {
await restoreBackupMutation.mutateAsync(backupId);
},
rebootNow: async () => {
await rebootMutation.mutateAsync(undefined);
},
shutdownNow: async () => {
await shutdownMutation.mutateAsync(undefined);
},
saveScheduledReboot: async (input: ScheduledRebootConfig) => {
await scheduledRebootMutation.mutateAsync(input);
},
pruneDockerImages: async () => {
await pruneImagesMutation.mutateAsync(undefined);
},
pruneDockerVolumes: async () => {
await pruneVolumesMutation.mutateAsync(undefined);
},
factoryReset: async () => {
await factoryResetMutation.mutateAsync(undefined);
},
},
};
}