refactoring(bw-limit): stylistic refactoring
This commit is contained in:
parent
cb72e6c073
commit
1622e34b0d
9 changed files with 1657 additions and 470 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -31,3 +31,5 @@ node_modules/
|
||||||
|
|
||||||
playwright/.auth
|
playwright/.auth
|
||||||
playwright/temp
|
playwright/temp
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,7 @@ import {
|
||||||
import { Input } from "../../../../components/ui/input";
|
import { Input } from "../../../../components/ui/input";
|
||||||
import { Textarea } from "../../../../components/ui/textarea";
|
import { Textarea } from "../../../../components/ui/textarea";
|
||||||
import { Checkbox } from "../../../../components/ui/checkbox";
|
import { Checkbox } from "../../../../components/ui/checkbox";
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../components/ui/select";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue
|
|
||||||
} from "../../../../components/ui/select";
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../components/ui/tooltip";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../../components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../../components/ui/collapsible";
|
||||||
import type { RepositoryFormValues } from "../create-repository-form";
|
import type { RepositoryFormValues } from "../create-repository-form";
|
||||||
|
|
@ -30,26 +24,15 @@ type Props = {
|
||||||
export const AdvancedForm = ({ form }: Props) => {
|
export const AdvancedForm = ({ form }: Props) => {
|
||||||
const insecureTls = form.watch("insecureTls");
|
const insecureTls = form.watch("insecureTls");
|
||||||
const cacert = form.watch("cacert");
|
const cacert = form.watch("cacert");
|
||||||
const uploadEnabled = form.watch("uploadLimit.enabled");
|
const uploadLimitEnabled = form.watch("uploadLimit.enabled");
|
||||||
const downloadEnabled = form.watch("downloadLimit.enabled");
|
const downloadLimitEnabled = form.watch("downloadLimit.enabled");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible>
|
<Collapsible>
|
||||||
<CollapsibleTrigger className="">Advanced Settings</CollapsibleTrigger>
|
<CollapsibleTrigger className="">Advanced Settings</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="pb-4 space-y-4">
|
<CollapsibleContent className="pb-4 space-y-4">
|
||||||
{/* Bandwidth Limit Controls */}
|
<div className="space-y-4 mt-4">
|
||||||
<div className="space-y-6 rounded-lg border bg-card p-6">
|
<div className="grid gap-6">
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-base font-semibold">Bandwidth Limits</h3>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Control upload and download speeds to prevent saturating network bandwidth
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-6 sm:grid-cols-2">
|
|
||||||
{/* Upload Limit */}
|
|
||||||
<div className="space-y-4 rounded-lg border bg-background/50 p-4">
|
<div className="space-y-4 rounded-lg border bg-background/50 p-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|
@ -57,155 +40,149 @@ export const AdvancedForm = ({ form }: Props) => {
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox
|
<Checkbox checked={field.value ?? false} onCheckedChange={field.onChange} />
|
||||||
checked={field.value ?? false}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div className="space-y-1 leading-none">
|
<div className="space-y-1">
|
||||||
<FormLabel>Enable upload speed limit</FormLabel>
|
<FormLabel>Enable upload speed limit</FormLabel>
|
||||||
<FormDescription className="text-xs">
|
<FormDescription className="text-xs">Limit upload speed to the repository</FormDescription>
|
||||||
Limit upload speed to the repository
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
{uploadEnabled && (
|
<FormField
|
||||||
<div className="space-y-3 pt-2">
|
control={form.control}
|
||||||
<div className="flex items-center gap-2">
|
name="uploadLimit.value"
|
||||||
<FormField
|
render={({ field }) => (
|
||||||
control={form.control}
|
<FormItem className="flex-1">
|
||||||
name="uploadLimit.value"
|
<FormControl>
|
||||||
render={({ field }) => (
|
<div className="relative">
|
||||||
<FormItem className="flex-1">
|
<Input
|
||||||
<FormControl>
|
disabled={!uploadLimitEnabled}
|
||||||
<div className="relative">
|
type="number"
|
||||||
<Input
|
min={1}
|
||||||
type="number"
|
step={1}
|
||||||
placeholder="10"
|
max={999999}
|
||||||
min="0"
|
placeholder="10"
|
||||||
step="0.1"
|
className="pr-12"
|
||||||
className="pr-12"
|
{...field}
|
||||||
{...field}
|
onChange={(e) => field.onChange(parseInt(e.target.value, 10) || 1)}
|
||||||
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
/>
|
||||||
/>
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
<div className="h-4 w-px bg-border" />
|
||||||
<div className="h-4 w-px bg-border" />
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</div>
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
</FormItem>
|
||||||
<FormField
|
)}
|
||||||
control={form.control}
|
/>
|
||||||
name="uploadLimit.unit"
|
<FormField
|
||||||
render={({ field }) => (
|
control={form.control}
|
||||||
<FormItem className="w-24">
|
name="uploadLimit.unit"
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value || "Mbps"} value={field.value || "Mbps"}>
|
render={({ field }) => (
|
||||||
<FormControl>
|
<FormItem className="w-24">
|
||||||
<SelectTrigger className="text-xs">
|
<Select
|
||||||
<SelectValue />
|
onValueChange={field.onChange}
|
||||||
</SelectTrigger>
|
defaultValue={field.value || "Mbps"}
|
||||||
</FormControl>
|
value={field.value || "Mbps"}
|
||||||
<SelectContent>
|
disabled={!uploadLimitEnabled}
|
||||||
{Object.keys(BANDWIDTH_UNITS).map((unit) => (
|
>
|
||||||
<SelectItem key={unit} value={unit} className="text-xs">
|
<FormControl>
|
||||||
{unit}
|
<SelectTrigger className="text-xs">
|
||||||
</SelectItem>
|
<SelectValue />
|
||||||
))}
|
</SelectTrigger>
|
||||||
</SelectContent>
|
</FormControl>
|
||||||
</Select>
|
<SelectContent>
|
||||||
<FormMessage />
|
{Object.keys(BANDWIDTH_UNITS).map((unit) => (
|
||||||
</FormItem>
|
<SelectItem key={unit} value={unit} className="text-xs">
|
||||||
)}
|
{unit}
|
||||||
/>
|
</SelectItem>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</SelectContent>
|
||||||
)}
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Download Limit */}
|
<div className="rounded-lg border bg-background/50 p-4">
|
||||||
<div className="space-y-4 rounded-lg border bg-background/50 p-4">
|
<FormField
|
||||||
|
control={form.control}
|
||||||
<FormField
|
name="downloadLimit.enabled"
|
||||||
control={form.control}
|
render={({ field }) => (
|
||||||
name="downloadLimit.enabled"
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
render={({ field }) => (
|
<FormControl>
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<Checkbox checked={field.value ?? false} onCheckedChange={field.onChange} />
|
||||||
<FormControl>
|
</FormControl>
|
||||||
<Checkbox
|
<div className="space-y-1">
|
||||||
checked={field.value ?? false}
|
<FormLabel>Enable download speed limit</FormLabel>
|
||||||
onCheckedChange={field.onChange}
|
<FormDescription className="text-xs">Limit download speed from the repository</FormDescription>
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="space-y-1 leading-none">
|
|
||||||
<FormLabel>Enable download speed limit</FormLabel>
|
|
||||||
<FormDescription className="text-xs">
|
|
||||||
Limit download speed from the repository
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{downloadEnabled && (
|
|
||||||
<div className="space-y-3 pt-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="downloadLimit.value"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder="10"
|
|
||||||
min="0"
|
|
||||||
step="0.1"
|
|
||||||
className="pr-12"
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
||||||
<div className="h-4 w-px bg-border" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="downloadLimit.unit"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="w-24">
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value || "Mbps"} value={field.value || "Mbps"}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger className="text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{Object.keys(BANDWIDTH_UNITS).map((unit) => (
|
|
||||||
<SelectItem key={unit} value={unit} className="text-xs">
|
|
||||||
{unit}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="downloadLimit.value"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex-1">
|
||||||
|
<FormControl>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
placeholder="10"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
step={1}
|
||||||
|
max={999999}
|
||||||
|
disabled={!downloadLimitEnabled}
|
||||||
|
className="pr-12"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(parseInt(e.target.value, 10) || 1)}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||||
|
<div className="h-4 w-px bg-border" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="downloadLimit.unit"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-24">
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value || "Mbps"}
|
||||||
|
value={field.value || "Mbps"}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger className="text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.keys(BANDWIDTH_UNITS).map((unit) => (
|
||||||
|
<SelectItem key={unit} value={unit} className="text-xs">
|
||||||
|
{unit}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ALTER TABLE `repositories_table` ADD `upload_limit_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
ALTER TABLE `repositories_table` ADD `upload_limit_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE `repositories_table` ADD `upload_limit_value` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
ALTER TABLE `repositories_table` ADD `upload_limit_value` real DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE `repositories_table` ADD `upload_limit_unit` text DEFAULT 'MB/s' NOT NULL;--> statement-breakpoint
|
ALTER TABLE `repositories_table` ADD `upload_limit_unit` text DEFAULT 'Mbps' NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE `repositories_table` ADD `download_limit_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
ALTER TABLE `repositories_table` ADD `download_limit_enabled` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE `repositories_table` ADD `download_limit_value` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
ALTER TABLE `repositories_table` ADD `download_limit_value` real DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||||
ALTER TABLE `repositories_table` ADD `download_limit_unit` text DEFAULT 'MB/s' NOT NULL;
|
ALTER TABLE `repositories_table` ADD `download_limit_unit` text DEFAULT 'Mbps' NOT NULL;
|
||||||
1243
app/drizzle/meta/0033_snapshot.json
Normal file
1243
app/drizzle/meta/0033_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,237 +1,244 @@
|
||||||
{
|
{
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1755765658194,
|
"when": 1755765658194,
|
||||||
"tag": "0000_known_madelyne_pryor",
|
"tag": "0000_known_madelyne_pryor",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1755775437391,
|
"when": 1755775437391,
|
||||||
"tag": "0001_far_frank_castle",
|
"tag": "0001_far_frank_castle",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1756930554198,
|
"when": 1756930554198,
|
||||||
"tag": "0002_cheerful_randall",
|
"tag": "0002_cheerful_randall",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1758653407064,
|
"when": 1758653407064,
|
||||||
"tag": "0003_mature_hellcat",
|
"tag": "0003_mature_hellcat",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 4,
|
"idx": 4,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1758961535488,
|
"when": 1758961535488,
|
||||||
"tag": "0004_wealthy_tomas",
|
"tag": "0004_wealthy_tomas",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 5,
|
"idx": 5,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1759416698274,
|
"when": 1759416698274,
|
||||||
"tag": "0005_simple_alice",
|
"tag": "0005_simple_alice",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 6,
|
"idx": 6,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1760734377440,
|
"when": 1760734377440,
|
||||||
"tag": "0006_secret_micromacro",
|
"tag": "0006_secret_micromacro",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 7,
|
"idx": 7,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1761224911352,
|
"when": 1761224911352,
|
||||||
"tag": "0007_watery_sersi",
|
"tag": "0007_watery_sersi",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 8,
|
"idx": 8,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1761414054481,
|
"when": 1761414054481,
|
||||||
"tag": "0008_silent_lady_bullseye",
|
"tag": "0008_silent_lady_bullseye",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 9,
|
"idx": 9,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1762095226041,
|
"when": 1762095226041,
|
||||||
"tag": "0009_little_adam_warlock",
|
"tag": "0009_little_adam_warlock",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 10,
|
"idx": 10,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1762610065889,
|
"when": 1762610065889,
|
||||||
"tag": "0010_perfect_proemial_gods",
|
"tag": "0010_perfect_proemial_gods",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 11,
|
"idx": 11,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1763644043601,
|
"when": 1763644043601,
|
||||||
"tag": "0011_familiar_stone_men",
|
"tag": "0011_familiar_stone_men",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 12,
|
"idx": 12,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764100562084,
|
"when": 1764100562084,
|
||||||
"tag": "0012_add_short_ids",
|
"tag": "0012_add_short_ids",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 13,
|
"idx": 13,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764182159797,
|
"when": 1764182159797,
|
||||||
"tag": "0013_elite_sprite",
|
"tag": "0013_elite_sprite",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 14,
|
"idx": 14,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764182405089,
|
"when": 1764182405089,
|
||||||
"tag": "0014_wild_echo",
|
"tag": "0014_wild_echo",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 15,
|
"idx": 15,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764182465287,
|
"when": 1764182465287,
|
||||||
"tag": "0015_jazzy_sersi",
|
"tag": "0015_jazzy_sersi",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 16,
|
"idx": 16,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764194697035,
|
"when": 1764194697035,
|
||||||
"tag": "0016_fix-timestamps-to-ms",
|
"tag": "0016_fix-timestamps-to-ms",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 17,
|
"idx": 17,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764357897219,
|
"when": 1764357897219,
|
||||||
"tag": "0017_fix-compression-modes",
|
"tag": "0017_fix-compression-modes",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 18,
|
"idx": 18,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764794371040,
|
"when": 1764794371040,
|
||||||
"tag": "0018_breezy_invaders",
|
"tag": "0018_breezy_invaders",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 19,
|
"idx": 19,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764839917446,
|
"when": 1764839917446,
|
||||||
"tag": "0019_secret_nomad",
|
"tag": "0019_secret_nomad",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 20,
|
"idx": 20,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1764847918249,
|
"when": 1764847918249,
|
||||||
"tag": "0020_even_dexter_bennett",
|
"tag": "0020_even_dexter_bennett",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 21,
|
"idx": 21,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1765307881092,
|
"when": 1765307881092,
|
||||||
"tag": "0021_steady_viper",
|
"tag": "0021_steady_viper",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 22,
|
"idx": 22,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1765794552191,
|
"when": 1765794552191,
|
||||||
"tag": "0022_woozy_shen",
|
"tag": "0022_woozy_shen",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 23,
|
"idx": 23,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1766320570509,
|
"when": 1766320570509,
|
||||||
"tag": "0023_special_thor",
|
"tag": "0023_special_thor",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 24,
|
"idx": 24,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1766325504548,
|
"when": 1766325504548,
|
||||||
"tag": "0024_schedules-one-fs",
|
"tag": "0024_schedules-one-fs",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 25,
|
"idx": 25,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1766431021321,
|
"when": 1766431021321,
|
||||||
"tag": "0025_remarkable_pete_wisdom",
|
"tag": "0025_remarkable_pete_wisdom",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 26,
|
"idx": 26,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1766765013108,
|
"when": 1766765013108,
|
||||||
"tag": "0026_migrate-local-repo-paths",
|
"tag": "0026_migrate-local-repo-paths",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 27,
|
"idx": 27,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1766778073418,
|
"when": 1766778073418,
|
||||||
"tag": "0027_careful_cammi",
|
"tag": "0027_careful_cammi",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 28,
|
"idx": 28,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1766778162985,
|
"when": 1766778162985,
|
||||||
"tag": "0028_third_amazoness",
|
"tag": "0028_third_amazoness",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 29,
|
"idx": 29,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1767819883495,
|
"when": 1767819883495,
|
||||||
"tag": "0029_boring_luke_cage",
|
"tag": "0029_boring_luke_cage",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 30,
|
"idx": 30,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1767821088612,
|
"when": 1767821088612,
|
||||||
"tag": "0030_lower-trim-username",
|
"tag": "0030_lower-trim-username",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 31,
|
"idx": 31,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1767863951955,
|
"when": 1767863951955,
|
||||||
"tag": "0031_graceful_squadron_supreme",
|
"tag": "0031_graceful_squadron_supreme",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 32,
|
"idx": 32,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1768136755563,
|
"when": 1768136755563,
|
||||||
"tag": "0032_lowercase-email",
|
"tag": "0032_lowercase-email",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
"idx": 33,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1768490085610,
|
||||||
|
"tag": "0033_curved_warhawk",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -14,17 +14,16 @@ export const REPOSITORY_BACKENDS = {
|
||||||
export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS;
|
export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS;
|
||||||
|
|
||||||
export const BANDWIDTH_UNITS = {
|
export const BANDWIDTH_UNITS = {
|
||||||
"Kbps": "Kbps",
|
Kbps: "Kbps",
|
||||||
"Mbps": "Mbps",
|
Mbps: "Mbps",
|
||||||
"Gbps": "Gbps",
|
Gbps: "Gbps",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type BandwidthUnit = keyof typeof BANDWIDTH_UNITS;
|
export type BandwidthUnit = keyof typeof BANDWIDTH_UNITS;
|
||||||
|
|
||||||
// Bandwidth limit configuration
|
|
||||||
export const bandwidthLimitSchema = type({
|
export const bandwidthLimitSchema = type({
|
||||||
enabled: "boolean = false",
|
enabled: "boolean = false",
|
||||||
value: "number >= 0 = 0",
|
value: "number > 0 = 1",
|
||||||
unit: type.valueOf(BANDWIDTH_UNITS).default("Mbps"),
|
unit: type.valueOf(BANDWIDTH_UNITS).default("Mbps"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { relations, sql } from "drizzle-orm";
|
import { relations, sql } from "drizzle-orm";
|
||||||
import { index, int, integer, sqliteTable, text, real, primaryKey, unique } from "drizzle-orm/sqlite-core";
|
import { index, int, integer, sqliteTable, text, real, primaryKey, unique } from "drizzle-orm/sqlite-core";
|
||||||
import type { CompressionMode, RepositoryBackend, repositoryConfigSchema, RepositoryStatus, BandwidthUnit } from "~/schemas/restic";
|
import type {
|
||||||
|
CompressionMode,
|
||||||
|
RepositoryBackend,
|
||||||
|
repositoryConfigSchema,
|
||||||
|
RepositoryStatus,
|
||||||
|
BandwidthUnit,
|
||||||
|
} from "~/schemas/restic";
|
||||||
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
|
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
|
||||||
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
|
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
|
||||||
|
|
||||||
|
|
@ -159,7 +165,6 @@ export const repositoriesTable = sqliteTable("repositories_table", {
|
||||||
status: text().$type<RepositoryStatus>().default("unknown"),
|
status: text().$type<RepositoryStatus>().default("unknown"),
|
||||||
lastChecked: int("last_checked", { mode: "number" }),
|
lastChecked: int("last_checked", { mode: "number" }),
|
||||||
lastError: text("last_error"),
|
lastError: text("last_error"),
|
||||||
// Bandwidth limit fields
|
|
||||||
uploadLimitEnabled: int("upload_limit_enabled", { mode: "boolean" }).notNull().default(false),
|
uploadLimitEnabled: int("upload_limit_enabled", { mode: "boolean" }).notNull().default(false),
|
||||||
uploadLimitValue: real("upload_limit_value").notNull().default(0),
|
uploadLimitValue: real("upload_limit_value").notNull().default(0),
|
||||||
uploadLimitUnit: text("upload_limit_unit").$type<BandwidthUnit>().notNull().default("Mbps"),
|
uploadLimitUnit: text("upload_limit_unit").$type<BandwidthUnit>().notNull().default("Mbps"),
|
||||||
|
|
|
||||||
|
|
@ -846,30 +846,13 @@ const copy = async (
|
||||||
args.push("latest");
|
args.push("latest");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply common args without automatic bandwidth limit injection to prevent duplication
|
|
||||||
addCommonArgs(args, env, destConfig, { skipBandwidth: true });
|
addCommonArgs(args, env, destConfig, { skipBandwidth: true });
|
||||||
|
|
||||||
// Manually handle bandwidth limits with correct copy semantics:
|
|
||||||
// --limit-download uses sourceConfig.downloadLimit (limiting downloads from source repo)
|
|
||||||
// --limit-upload uses destConfig.uploadLimit (limiting uploads to destination repo)
|
|
||||||
const sourceDownloadLimit = formatBandwidthLimit(sourceConfig.downloadLimit);
|
const sourceDownloadLimit = formatBandwidthLimit(sourceConfig.downloadLimit);
|
||||||
const destUploadLimit = formatBandwidthLimit(destConfig.uploadLimit);
|
const destUploadLimit = formatBandwidthLimit(destConfig.uploadLimit);
|
||||||
|
|
||||||
if (sourceConfig.backend === "rclone") {
|
if (sourceDownloadLimit) {
|
||||||
// For rclone source backends, use rclone.from.bwlimit with source download limit only
|
args.push("--limit-download", sourceDownloadLimit);
|
||||||
let effectiveSourceLimit = "";
|
|
||||||
if (sourceDownloadLimit) {
|
|
||||||
effectiveSourceLimit = sourceDownloadLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectiveSourceLimit) {
|
|
||||||
args.push("-o", `rclone.from.bwlimit=${effectiveSourceLimit}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For restic source backends, apply download limit from source
|
|
||||||
if (sourceDownloadLimit) {
|
|
||||||
args.push("--limit-download", sourceDownloadLimit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply upload limit to destination for all backends
|
// Apply upload limit to destination for all backends
|
||||||
|
|
@ -899,13 +882,11 @@ const copy = async (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to convert bandwidth limit to restic/rclone format
|
|
||||||
const formatBandwidthLimit = (limit?: BandwidthLimit): string => {
|
const formatBandwidthLimit = (limit?: BandwidthLimit): string => {
|
||||||
if (!limit || !limit.enabled || limit.value <= 0) {
|
if (!limit || !limit.enabled || limit.value <= 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to KiB/s for restic compatibility, or use suffixed format for rclone
|
|
||||||
let kibibytesPerSecond: number;
|
let kibibytesPerSecond: number;
|
||||||
switch (limit.unit) {
|
switch (limit.unit) {
|
||||||
case "Kbps":
|
case "Kbps":
|
||||||
|
|
@ -924,11 +905,15 @@ const formatBandwidthLimit = (limit?: BandwidthLimit): string => {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return as integer KiB/s for restic
|
|
||||||
return `${Math.floor(kibibytesPerSecond)}`;
|
return `${Math.floor(kibibytesPerSecond)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addCommonArgs = (args: string[], env: Record<string, string>, config?: RepositoryConfig, options?: { skipBandwidth?: boolean }) => {
|
export const addCommonArgs = (
|
||||||
|
args: string[],
|
||||||
|
env: Record<string, string>,
|
||||||
|
config?: RepositoryConfig,
|
||||||
|
options?: { skipBandwidth?: boolean },
|
||||||
|
) => {
|
||||||
args.push("--json");
|
args.push("--json");
|
||||||
|
|
||||||
if (env._SFTP_SSH_ARGS) {
|
if (env._SFTP_SSH_ARGS) {
|
||||||
|
|
@ -943,37 +928,15 @@ export const addCommonArgs = (args: string[], env: Record<string, string>, confi
|
||||||
args.push("--cacert", env.RESTIC_CACERT);
|
args.push("--cacert", env.RESTIC_CACERT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add bandwidth limits if configuration is provided and not skipped
|
|
||||||
if (config && !options?.skipBandwidth) {
|
if (config && !options?.skipBandwidth) {
|
||||||
if (config.backend === "rclone") {
|
const uploadLimit = formatBandwidthLimit(config.uploadLimit);
|
||||||
// For rclone backends, consolidate both upload and download limits into one bwlimit
|
if (uploadLimit) {
|
||||||
const uploadLimit = formatBandwidthLimit(config.uploadLimit);
|
args.push("--limit-upload", uploadLimit);
|
||||||
const downloadLimit = formatBandwidthLimit(config.downloadLimit);
|
}
|
||||||
|
|
||||||
// Determine effective limit (choose more restrictive when both exist)
|
const downloadLimit = formatBandwidthLimit(config.downloadLimit);
|
||||||
let effectiveLimit = "";
|
if (downloadLimit) {
|
||||||
if (uploadLimit && downloadLimit) {
|
args.push("--limit-download", downloadLimit);
|
||||||
effectiveLimit = parseInt(uploadLimit, 10) < parseInt(downloadLimit, 10) ? uploadLimit : downloadLimit;
|
|
||||||
} else if (uploadLimit) {
|
|
||||||
effectiveLimit = uploadLimit;
|
|
||||||
} else if (downloadLimit) {
|
|
||||||
effectiveLimit = downloadLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectiveLimit) {
|
|
||||||
args.push("-o", `rclone.bwlimit=${effectiveLimit}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For restic backends, handle upload and download limits separately
|
|
||||||
const uploadLimit = formatBandwidthLimit(config.uploadLimit);
|
|
||||||
if (uploadLimit) {
|
|
||||||
args.push("--limit-upload", uploadLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadLimit = formatBandwidthLimit(config.downloadLimit);
|
|
||||||
if (downloadLimit) {
|
|
||||||
args.push("--limit-download", downloadLimit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -995,26 +958,17 @@ export const restic = {
|
||||||
copy,
|
copy,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to clean up temporary files
|
|
||||||
export const cleanupTemporaryKeys = async (env: Record<string, string>) => {
|
export const cleanupTemporaryKeys = async (env: Record<string, string>) => {
|
||||||
const keysToClean = ["_SFTP_KEY_PATH", "_SFTP_KNOWN_HOSTS_PATH", "RESTIC_CACERT", "GOOGLE_APPLICATION_CREDENTIALS"];
|
const keysToClean = ["_SFTP_KEY_PATH", "_SFTP_KNOWN_HOSTS_PATH", "RESTIC_CACERT", "GOOGLE_APPLICATION_CREDENTIALS"];
|
||||||
|
|
||||||
for (const key of keysToClean) {
|
for (const key of keysToClean) {
|
||||||
if (env[key]) {
|
if (env[key]) {
|
||||||
try {
|
await fs.unlink(env[key]).catch(() => {});
|
||||||
await fs.unlink(env[key]);
|
|
||||||
} catch (_error) {
|
|
||||||
// Ignore errors when cleaning up temporary files
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up custom password files
|
// Clean up custom password files
|
||||||
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== RESTIC_PASS_FILE) {
|
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== RESTIC_PASS_FILE) {
|
||||||
try {
|
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
||||||
await fs.unlink(env.RESTIC_PASSWORD_FILE);
|
|
||||||
} catch (_error) {
|
|
||||||
// Ignore errors when cleaning up temporary files
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue