feat(yucca sdk): freeform bandwidth limit (#713)

This commit is contained in:
Paul Makles
2026-09-28 14:13:20 +01:00
committed by GitHub
parent 91860be6be
commit 8c20fe5856
3 changed files with 47 additions and 39 deletions
@@ -5,7 +5,7 @@
defaultQuietHoursStart,
toBandwidthDto,
toBandwidthForm,
unlimitedBandwidth,
toBytesPerSec,
useConfig,
useUpdateConfig,
} from "$lib/services/config.service";
@@ -14,6 +14,7 @@
Field,
HStack,
LoadingSpinner,
NumberInput,
Select,
Stack,
Switch,
@@ -21,16 +22,6 @@
} from "@immich/ui";
import { mdiSpeedometerSlow } from "@mdi/js";
const bytesPerMegabyte = 1_000_000;
const speeds = [
{ value: unlimitedBandwidth, label: "No limit" },
...[0.5, 1, 2, 5, 10, 20, 50, 100].map((megabytes) => ({
value: String(megabytes * bytesPerMegabyte),
label: `${megabytes} MB/s`,
})),
];
const hours = Array.from({ length: 24 }, (_, hour) => ({
value: `${String(hour).padStart(2, "0")}:00`,
label: `${String(hour).padStart(2, "0")}:00`,
@@ -39,7 +30,7 @@
const config = useConfig();
const mutation = useUpdateConfig();
let speed = $state(unlimitedBandwidth);
let mbps = $state<number>();
let quiet = $state(false);
let quietStart = $state(defaultQuietHoursStart);
let quietEnd = $state(defaultQuietHoursEnd);
@@ -50,7 +41,7 @@
return;
}
({ speed, quiet, quietStart, quietEnd } = toBandwidthForm(
({ mbps, quiet, quietStart, quietEnd } = toBandwidthForm(
config.data.bandwidth,
));
};
@@ -64,11 +55,11 @@
load();
});
const limited = $derived(speed !== unlimitedBandwidth);
const limited = $derived(toBytesPerSec(mbps) > 0);
const onSave = () => {
mutation.mutate({
bandwidth: toBandwidthDto({ speed, quiet, quietStart, quietEnd }),
bandwidth: toBandwidthDto({ mbps, quiet, quietStart, quietEnd }),
});
};
</script>
@@ -84,13 +75,15 @@
{:else}
<Field
label="Upload speed"
description="Slow down backup uploads without affecting restores."
description="Slow down backup uploads without affecting restores. Leave empty for no limit."
color="primary"
>
<Select
options={speeds}
value={speed}
onChange={(value) => (speed = value)}
<NumberInput
bind:value={mbps}
min={0}
step={1}
placeholder="No limit"
trailingText="Mbps"
/>
</Field>
@@ -15,7 +15,7 @@ describe('toBandwidthForm', () => {
expect(
toBandwidthForm({ bytesPerSec: 5_000_000, quietHours: '23:00-07:00' }),
).toEqual({
speed: '5000000',
mbps: 40,
quiet: true,
quietStart: '23:00',
quietEnd: '07:00',
@@ -24,7 +24,7 @@ describe('toBandwidthForm', () => {
it('falls back to the default quiet hours when none are saved', () => {
expect(toBandwidthForm({ bytesPerSec: 0 })).toEqual({
speed: '0',
mbps: undefined,
quiet: false,
quietStart: '22:00',
quietEnd: '06:00',
@@ -33,10 +33,10 @@ describe('toBandwidthForm', () => {
});
describe('toBandwidthDto', () => {
it('converts the selected speed to bytes per second with quiet hours', () => {
it('converts megabits per second to bytes per second with quiet hours', () => {
expect(
toBandwidthDto({
speed: '500000',
mbps: 4,
quiet: true,
quietStart: '22:00',
quietEnd: '06:00',
@@ -47,7 +47,7 @@ describe('toBandwidthDto', () => {
it('clears quiet hours when they are switched off', () => {
expect(
toBandwidthDto({
speed: '2000000',
mbps: 16,
quiet: false,
quietStart: '22:00',
quietEnd: '06:00',
@@ -58,7 +58,18 @@ describe('toBandwidthDto', () => {
it('disables the limit and drops quiet hours when set to no limit', () => {
expect(
toBandwidthDto({
speed: '0',
mbps: 0,
quiet: true,
quietStart: '22:00',
quietEnd: '06:00',
}),
).toEqual({ bytesPerSec: 0, quietHours: undefined });
});
it('treats an empty speed as no limit', () => {
expect(
toBandwidthDto({
mbps: undefined,
quiet: true,
quietStart: '22:00',
quietEnd: '06:00',
@@ -87,7 +98,7 @@ describe('useUpdateConfig', () => {
useUpdateConfig().mutate({
bandwidth: toBandwidthDto({
speed: '1000000',
mbps: 8,
quiet: true,
quietStart: '22:00',
quietEnd: '06:00',
@@ -106,7 +117,7 @@ describe('useUpdateConfig', () => {
useUpdateConfig().mutate({
bandwidth: toBandwidthDto({
speed: '0',
mbps: 0,
quiet: true,
quietStart: '22:00',
quietEnd: '06:00',
@@ -12,12 +12,12 @@ export const configKeys = {
all: ['config'] as const,
};
export const unlimitedBandwidth = '0';
const bytesPerSecPerMbps = 125_000;
export const defaultQuietHoursStart = '22:00';
export const defaultQuietHoursEnd = '06:00';
export type BandwidthForm = {
speed: string;
mbps?: number;
quiet: boolean;
quietStart: string;
quietEnd: string;
@@ -31,25 +31,29 @@ export const toBandwidthForm = ({
quietHours?.split('-') ?? [];
return {
speed: String(bytesPerSec),
mbps: bytesPerSec ? bytesPerSec / bytesPerSecPerMbps : undefined,
quiet: Boolean(quietHours),
quietStart,
quietEnd,
};
};
export const toBytesPerSec = (mbps = 0) =>
Math.max(0, Math.ceil(mbps)) * bytesPerSecPerMbps;
export const toBandwidthDto = ({
speed,
mbps,
quiet,
quietStart,
quietEnd,
}: BandwidthForm): BandwidthDto => ({
bytesPerSec: Number(speed),
quietHours:
speed !== unlimitedBandwidth && quiet
? `${quietStart}-${quietEnd}`
: undefined,
});
}: BandwidthForm): BandwidthDto => {
const bytesPerSec = toBytesPerSec(mbps);
return {
bytesPerSec,
quietHours: bytesPerSec && quiet ? `${quietStart}-${quietEnd}` : undefined,
};
};
export const useConfig = () =>
createQuery(