Merge branch 'Rajdave69-main'
This commit is contained in:
commit
31719103f6
8 changed files with 1764 additions and 270 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -31,3 +31,5 @@ node_modules/
|
|||
|
||||
playwright/.auth
|
||||
playwright/temp
|
||||
|
||||
.idea/
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@ import {
|
|||
FormLabel,
|
||||
FormMessage,
|
||||
} from "../../../../components/ui/form";
|
||||
import { Input } from "../../../../components/ui/input";
|
||||
import { Textarea } from "../../../../components/ui/textarea";
|
||||
import { Checkbox } from "../../../../components/ui/checkbox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../components/ui/select";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../components/ui/tooltip";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../../components/ui/collapsible";
|
||||
import type { RepositoryFormValues } from "../create-repository-form";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
import { BANDWIDTH_UNITS } from "~/schemas/restic";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturn<RepositoryFormValues>;
|
||||
|
|
@ -21,11 +24,169 @@ type Props = {
|
|||
export const AdvancedForm = ({ form }: Props) => {
|
||||
const insecureTls = form.watch("insecureTls");
|
||||
const cacert = form.watch("cacert");
|
||||
const uploadLimitEnabled = form.watch("uploadLimit.enabled");
|
||||
const downloadLimitEnabled = form.watch("downloadLimit.enabled");
|
||||
|
||||
return (
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="">Advanced Settings</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pb-4 space-y-4">
|
||||
<div className="space-y-4 mt-4">
|
||||
<div className="grid gap-6">
|
||||
<div className="space-y-4 rounded-lg border bg-background/50 p-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="uploadLimit.enabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox checked={field.value ?? false} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
<div className="space-y-1">
|
||||
<FormLabel>Enable upload speed limit</FormLabel>
|
||||
<FormDescription className="text-xs">Limit upload speed to the repository</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="uploadLimit.value"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
disabled={!uploadLimitEnabled}
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
max={999999}
|
||||
placeholder="10"
|
||||
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>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="uploadLimit.unit"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-24">
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value || "Mbps"}
|
||||
value={field.value || "Mbps"}
|
||||
disabled={!uploadLimitEnabled}
|
||||
>
|
||||
<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 className="rounded-lg border bg-background/50 p-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="downloadLimit.enabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox checked={field.value ?? false} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
<div className="space-y-1">
|
||||
<FormLabel>Enable download speed limit</FormLabel>
|
||||
<FormDescription className="text-xs">Limit download speed from the repository</FormDescription>
|
||||
</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>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="insecureTls"
|
||||
|
|
|
|||
6
app/drizzle/0033_thankful_master_chief.sql
Normal file
6
app/drizzle/0033_thankful_master_chief.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE `repositories_table` ADD `upload_limit_enabled` integer DEFAULT false 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 '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_value` real DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
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",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1755765658194,
|
||||
"tag": "0000_known_madelyne_pryor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1755775437391,
|
||||
"tag": "0001_far_frank_castle",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "6",
|
||||
"when": 1756930554198,
|
||||
"tag": "0002_cheerful_randall",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "6",
|
||||
"when": 1758653407064,
|
||||
"tag": "0003_mature_hellcat",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "6",
|
||||
"when": 1758961535488,
|
||||
"tag": "0004_wealthy_tomas",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "6",
|
||||
"when": 1759416698274,
|
||||
"tag": "0005_simple_alice",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "6",
|
||||
"when": 1760734377440,
|
||||
"tag": "0006_secret_micromacro",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "6",
|
||||
"when": 1761224911352,
|
||||
"tag": "0007_watery_sersi",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "6",
|
||||
"when": 1761414054481,
|
||||
"tag": "0008_silent_lady_bullseye",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "6",
|
||||
"when": 1762095226041,
|
||||
"tag": "0009_little_adam_warlock",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "6",
|
||||
"when": 1762610065889,
|
||||
"tag": "0010_perfect_proemial_gods",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "6",
|
||||
"when": 1763644043601,
|
||||
"tag": "0011_familiar_stone_men",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "6",
|
||||
"when": 1764100562084,
|
||||
"tag": "0012_add_short_ids",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "6",
|
||||
"when": 1764182159797,
|
||||
"tag": "0013_elite_sprite",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "6",
|
||||
"when": 1764182405089,
|
||||
"tag": "0014_wild_echo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "6",
|
||||
"when": 1764182465287,
|
||||
"tag": "0015_jazzy_sersi",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "6",
|
||||
"when": 1764194697035,
|
||||
"tag": "0016_fix-timestamps-to-ms",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "6",
|
||||
"when": 1764357897219,
|
||||
"tag": "0017_fix-compression-modes",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "6",
|
||||
"when": 1764794371040,
|
||||
"tag": "0018_breezy_invaders",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 19,
|
||||
"version": "6",
|
||||
"when": 1764839917446,
|
||||
"tag": "0019_secret_nomad",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 20,
|
||||
"version": "6",
|
||||
"when": 1764847918249,
|
||||
"tag": "0020_even_dexter_bennett",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 21,
|
||||
"version": "6",
|
||||
"when": 1765307881092,
|
||||
"tag": "0021_steady_viper",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 22,
|
||||
"version": "6",
|
||||
"when": 1765794552191,
|
||||
"tag": "0022_woozy_shen",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 23,
|
||||
"version": "6",
|
||||
"when": 1766320570509,
|
||||
"tag": "0023_special_thor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 24,
|
||||
"version": "6",
|
||||
"when": 1766325504548,
|
||||
"tag": "0024_schedules-one-fs",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 25,
|
||||
"version": "6",
|
||||
"when": 1766431021321,
|
||||
"tag": "0025_remarkable_pete_wisdom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 26,
|
||||
"version": "6",
|
||||
"when": 1766765013108,
|
||||
"tag": "0026_migrate-local-repo-paths",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 27,
|
||||
"version": "6",
|
||||
"when": 1766778073418,
|
||||
"tag": "0027_careful_cammi",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 28,
|
||||
"version": "6",
|
||||
"when": 1766778162985,
|
||||
"tag": "0028_third_amazoness",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 29,
|
||||
"version": "6",
|
||||
"when": 1767819883495,
|
||||
"tag": "0029_boring_luke_cage",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 30,
|
||||
"version": "6",
|
||||
"when": 1767821088612,
|
||||
"tag": "0030_lower-trim-username",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 31,
|
||||
"version": "6",
|
||||
"when": 1767863951955,
|
||||
"tag": "0031_graceful_squadron_supreme",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 32,
|
||||
"version": "6",
|
||||
"when": 1768136755563,
|
||||
"tag": "0032_lowercase-email",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1755765658194,
|
||||
"tag": "0000_known_madelyne_pryor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1755775437391,
|
||||
"tag": "0001_far_frank_castle",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "6",
|
||||
"when": 1756930554198,
|
||||
"tag": "0002_cheerful_randall",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "6",
|
||||
"when": 1758653407064,
|
||||
"tag": "0003_mature_hellcat",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "6",
|
||||
"when": 1758961535488,
|
||||
"tag": "0004_wealthy_tomas",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "6",
|
||||
"when": 1759416698274,
|
||||
"tag": "0005_simple_alice",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "6",
|
||||
"when": 1760734377440,
|
||||
"tag": "0006_secret_micromacro",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "6",
|
||||
"when": 1761224911352,
|
||||
"tag": "0007_watery_sersi",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "6",
|
||||
"when": 1761414054481,
|
||||
"tag": "0008_silent_lady_bullseye",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "6",
|
||||
"when": 1762095226041,
|
||||
"tag": "0009_little_adam_warlock",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "6",
|
||||
"when": 1762610065889,
|
||||
"tag": "0010_perfect_proemial_gods",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "6",
|
||||
"when": 1763644043601,
|
||||
"tag": "0011_familiar_stone_men",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "6",
|
||||
"when": 1764100562084,
|
||||
"tag": "0012_add_short_ids",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "6",
|
||||
"when": 1764182159797,
|
||||
"tag": "0013_elite_sprite",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "6",
|
||||
"when": 1764182405089,
|
||||
"tag": "0014_wild_echo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "6",
|
||||
"when": 1764182465287,
|
||||
"tag": "0015_jazzy_sersi",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "6",
|
||||
"when": 1764194697035,
|
||||
"tag": "0016_fix-timestamps-to-ms",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "6",
|
||||
"when": 1764357897219,
|
||||
"tag": "0017_fix-compression-modes",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "6",
|
||||
"when": 1764794371040,
|
||||
"tag": "0018_breezy_invaders",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 19,
|
||||
"version": "6",
|
||||
"when": 1764839917446,
|
||||
"tag": "0019_secret_nomad",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 20,
|
||||
"version": "6",
|
||||
"when": 1764847918249,
|
||||
"tag": "0020_even_dexter_bennett",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 21,
|
||||
"version": "6",
|
||||
"when": 1765307881092,
|
||||
"tag": "0021_steady_viper",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 22,
|
||||
"version": "6",
|
||||
"when": 1765794552191,
|
||||
"tag": "0022_woozy_shen",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 23,
|
||||
"version": "6",
|
||||
"when": 1766320570509,
|
||||
"tag": "0023_special_thor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 24,
|
||||
"version": "6",
|
||||
"when": 1766325504548,
|
||||
"tag": "0024_schedules-one-fs",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 25,
|
||||
"version": "6",
|
||||
"when": 1766431021321,
|
||||
"tag": "0025_remarkable_pete_wisdom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 26,
|
||||
"version": "6",
|
||||
"when": 1766765013108,
|
||||
"tag": "0026_migrate-local-repo-paths",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 27,
|
||||
"version": "6",
|
||||
"when": 1766778073418,
|
||||
"tag": "0027_careful_cammi",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 28,
|
||||
"version": "6",
|
||||
"when": 1766778162985,
|
||||
"tag": "0028_third_amazoness",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 29,
|
||||
"version": "6",
|
||||
"when": 1767819883495,
|
||||
"tag": "0029_boring_luke_cage",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 30,
|
||||
"version": "6",
|
||||
"when": 1767821088612,
|
||||
"tag": "0030_lower-trim-username",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 31,
|
||||
"version": "6",
|
||||
"when": 1767863951955,
|
||||
"tag": "0031_graceful_squadron_supreme",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 32,
|
||||
"version": "6",
|
||||
"when": 1768136755563,
|
||||
"tag": "0032_lowercase-email",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 33,
|
||||
"version": "6",
|
||||
"when": 1768497229303,
|
||||
"tag": "0033_thankful_master_chief",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -13,12 +13,31 @@ export const REPOSITORY_BACKENDS = {
|
|||
|
||||
export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS;
|
||||
|
||||
export const BANDWIDTH_UNITS = {
|
||||
Kbps: "Kbps",
|
||||
Mbps: "Mbps",
|
||||
Gbps: "Gbps",
|
||||
} as const;
|
||||
|
||||
export type BandwidthUnit = keyof typeof BANDWIDTH_UNITS;
|
||||
|
||||
export const bandwidthLimitSchema = type({
|
||||
enabled: "boolean = false",
|
||||
value: "number > 0 = 1",
|
||||
unit: type.valueOf(BANDWIDTH_UNITS).default("Mbps"),
|
||||
});
|
||||
|
||||
export type BandwidthLimit = typeof bandwidthLimitSchema.infer;
|
||||
|
||||
// Common fields for all repository configs
|
||||
const baseRepositoryConfigSchema = type({
|
||||
isExistingRepository: "boolean?",
|
||||
customPassword: "string?",
|
||||
cacert: "string?",
|
||||
insecureTls: "boolean?",
|
||||
// Bandwidth controls
|
||||
uploadLimit: bandwidthLimitSchema.optional(),
|
||||
downloadLimit: bandwidthLimitSchema.optional(),
|
||||
});
|
||||
|
||||
export const s3RepositoryConfigSchema = type({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { relations, sql } from "drizzle-orm";
|
||||
import { index, int, integer, sqliteTable, text, primaryKey, unique } from "drizzle-orm/sqlite-core";
|
||||
import type { CompressionMode, RepositoryBackend, repositoryConfigSchema, RepositoryStatus } from "~/schemas/restic";
|
||||
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 { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
|
||||
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
|
||||
|
||||
|
|
@ -159,6 +165,12 @@ export const repositoriesTable = sqliteTable("repositories_table", {
|
|||
status: text().$type<RepositoryStatus>().default("unknown"),
|
||||
lastChecked: int("last_checked", { mode: "number" }),
|
||||
lastError: text("last_error"),
|
||||
uploadLimitEnabled: int("upload_limit_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
uploadLimitValue: real("upload_limit_value").notNull().default(0),
|
||||
uploadLimitUnit: text("upload_limit_unit").$type<BandwidthUnit>().notNull().default("Mbps"),
|
||||
downloadLimitEnabled: int("download_limit_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
downloadLimitValue: real("download_limit_value").notNull().default(0),
|
||||
downloadLimitUnit: text("download_limit_unit").$type<BandwidthUnit>().notNull().default("Mbps"),
|
||||
createdAt: int("created_at", { mode: "number" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch() * 1000)`),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { logger } from "./logger";
|
|||
import { cryptoUtils } from "./crypto";
|
||||
import type { RetentionPolicy } from "../modules/backups/backups.dto";
|
||||
import { safeSpawn } from "./spawn";
|
||||
import type { CompressionMode, RepositoryConfig, OverwriteMode } from "~/schemas/restic";
|
||||
import type { CompressionMode, RepositoryConfig, OverwriteMode, BandwidthLimit } from "~/schemas/restic";
|
||||
import { ResticError } from "./errors";
|
||||
|
||||
const backupOutputSchema = type({
|
||||
|
|
@ -229,7 +229,7 @@ const init = async (config: RepositoryConfig) => {
|
|||
const env = await buildEnv(config);
|
||||
|
||||
const args = ["init", "--repo", repoUrl];
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -321,7 +321,7 @@ const backup = async (
|
|||
}
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const logData = throttle((data: string) => {
|
||||
logger.info(data.trim());
|
||||
|
|
@ -453,7 +453,7 @@ const restore = async (
|
|||
}
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
logger.debug(`Executing: restic ${args.join(" ")}`);
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
|
|
@ -516,7 +516,7 @@ const snapshots = async (config: RepositoryConfig, options: { tags?: string[] }
|
|||
}
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -565,7 +565,7 @@ const forget = async (config: RepositoryConfig, options: RetentionPolicy, extra:
|
|||
}
|
||||
|
||||
args.push("--prune");
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -587,7 +587,7 @@ const deleteSnapshots = async (config: RepositoryConfig, snapshotIds: string[])
|
|||
}
|
||||
|
||||
const args: string[] = ["--repo", repoUrl, "forget", ...snapshotIds, "--prune"];
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -636,7 +636,7 @@ const tagSnapshots = async (
|
|||
}
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -686,7 +686,7 @@ const ls = async (config: RepositoryConfig, snapshotId: string, path?: string) =
|
|||
args.push(path);
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -737,7 +737,7 @@ const unlock = async (config: RepositoryConfig) => {
|
|||
const env = await buildEnv(config);
|
||||
|
||||
const args = ["unlock", "--repo", repoUrl, "--remove-all"];
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -761,7 +761,7 @@ const check = async (config: RepositoryConfig, options?: { readData?: boolean })
|
|||
args.push("--read-data");
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -794,7 +794,7 @@ const repairIndex = async (config: RepositoryConfig) => {
|
|||
const env = await buildEnv(config);
|
||||
|
||||
const args = ["repair", "index", "--repo", repoUrl];
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, config);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
|
@ -846,10 +846,18 @@ const copy = async (
|
|||
args.push("latest");
|
||||
}
|
||||
|
||||
addCommonArgs(args, env);
|
||||
addCommonArgs(args, env, destConfig, { skipBandwidth: true });
|
||||
|
||||
if (sourceConfig.backend === "sftp" && sourceEnv._SFTP_SSH_ARGS) {
|
||||
args.push("-o", `sftp.args=${sourceEnv._SFTP_SSH_ARGS}`);
|
||||
const sourceDownloadLimit = formatBandwidthLimit(sourceConfig.downloadLimit);
|
||||
const destUploadLimit = formatBandwidthLimit(destConfig.uploadLimit);
|
||||
|
||||
if (sourceDownloadLimit) {
|
||||
args.push("--limit-download", sourceDownloadLimit);
|
||||
}
|
||||
|
||||
// Apply upload limit to destination for all backends
|
||||
if (destUploadLimit) {
|
||||
args.push("--limit-upload", destUploadLimit);
|
||||
}
|
||||
|
||||
logger.info(`Copying snapshots from ${sourceRepoUrl} to ${destRepoUrl}...`);
|
||||
|
|
@ -874,29 +882,38 @@ const copy = async (
|
|||
};
|
||||
};
|
||||
|
||||
export const cleanupTemporaryKeys = async (env: Record<string, string>) => {
|
||||
if (env._SFTP_KEY_PATH) {
|
||||
await fs.unlink(env._SFTP_KEY_PATH).catch(() => {});
|
||||
const formatBandwidthLimit = (limit?: BandwidthLimit): string => {
|
||||
if (!limit || !limit.enabled || limit.value <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (env._SFTP_KNOWN_HOSTS_PATH) {
|
||||
await fs.unlink(env._SFTP_KNOWN_HOSTS_PATH).catch(() => {});
|
||||
let kibibytesPerSecond: number;
|
||||
switch (limit.unit) {
|
||||
case "Kbps":
|
||||
// Kilobits per second to KiB/s: kilobits→bits→bytes→KiB
|
||||
kibibytesPerSecond = (limit.value * 1000) / 8 / 1024;
|
||||
break;
|
||||
case "Mbps":
|
||||
// Megabits per second to KiB/s: multiply by 1000000 (Mb to bits), divide by 8 (bits to bytes), then by 1024 (bytes to KiB)
|
||||
kibibytesPerSecond = (limit.value * 1000000) / (8 * 1024);
|
||||
break;
|
||||
case "Gbps":
|
||||
// Gigabits per second to KiB/s: multiply by 1000000000 (Gb to bits), divide by 8 (bits to bytes), then by 1024 (bytes to KiB)
|
||||
kibibytesPerSecond = (limit.value * 1000000000) / (8 * 1024);
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== RESTIC_PASS_FILE) {
|
||||
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
||||
}
|
||||
|
||||
if (env.GOOGLE_APPLICATION_CREDENTIALS) {
|
||||
await fs.unlink(env.GOOGLE_APPLICATION_CREDENTIALS).catch(() => {});
|
||||
}
|
||||
|
||||
if (env.RESTIC_CACERT) {
|
||||
await fs.unlink(env.RESTIC_CACERT).catch(() => {});
|
||||
}
|
||||
return `${Math.floor(kibibytesPerSecond)}`;
|
||||
};
|
||||
|
||||
export const addCommonArgs = (args: string[], env: Record<string, string>) => {
|
||||
export const addCommonArgs = (
|
||||
args: string[],
|
||||
env: Record<string, string>,
|
||||
config?: RepositoryConfig,
|
||||
options?: { skipBandwidth?: boolean },
|
||||
) => {
|
||||
args.push("--json");
|
||||
|
||||
if (env._SFTP_SSH_ARGS) {
|
||||
|
|
@ -910,6 +927,18 @@ export const addCommonArgs = (args: string[], env: Record<string, string>) => {
|
|||
if (env.RESTIC_CACERT) {
|
||||
args.push("--cacert", env.RESTIC_CACERT);
|
||||
}
|
||||
|
||||
if (config && !options?.skipBandwidth) {
|
||||
const uploadLimit = formatBandwidthLimit(config.uploadLimit);
|
||||
if (uploadLimit) {
|
||||
args.push("--limit-upload", uploadLimit);
|
||||
}
|
||||
|
||||
const downloadLimit = formatBandwidthLimit(config.downloadLimit);
|
||||
if (downloadLimit) {
|
||||
args.push("--limit-download", downloadLimit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const restic = {
|
||||
|
|
@ -928,3 +957,18 @@ export const restic = {
|
|||
repairIndex,
|
||||
copy,
|
||||
};
|
||||
|
||||
export const cleanupTemporaryKeys = async (env: Record<string, string>) => {
|
||||
const keysToClean = ["_SFTP_KEY_PATH", "_SFTP_KNOWN_HOSTS_PATH", "RESTIC_CACERT", "GOOGLE_APPLICATION_CREDENTIALS"];
|
||||
|
||||
for (const key of keysToClean) {
|
||||
if (env[key]) {
|
||||
await fs.unlink(env[key]).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up custom password files
|
||||
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== RESTIC_PASS_FILE) {
|
||||
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue