Compare commits

...

18 Commits

Author SHA1 Message Date
Nicolas Meienberger
7091f1f296 chore: remove retry-lock 2025-12-06 11:17:31 +01:00
Nicolas Meienberger
b394aca7ca Merge branch 'tvarohohlavy-missing-icons-2' 2025-12-06 11:13:51 +01:00
Nicolas Meienberger
fdb84374a0 refactor: remove icons where it was making the context worse 2025-12-06 11:13:26 +01:00
Nicolas Meienberger
c42380b26b refactor: remove trim on password
The password should be taken as-is. It could potentially contain a space
2025-12-06 10:36:39 +01:00
Nicolas Meienberger
51ed47c30f refactor: no need to print safe args as it's already sanitized 2025-12-06 10:36:39 +01:00
Nicolas Meienberger
b8ae10b316 fix: volume data not refreshing when changing selection 2025-12-06 10:36:39 +01:00
Nico
f232fc07c1 feat: custom include patterns (#104)
* feat: add custom include patterns

* feat: add exclude-if-present option
2025-12-06 10:36:39 +01:00
Nico
d542318e2c feat: naming backup schedules (#103)
* docs: add agents instructions

* feat: naming backup schedules

* fix: wrong table for filtering
2025-12-06 10:36:39 +01:00
Nicolas Meienberger
2660d91002 fix: broken migration 2025-12-06 10:36:25 +01:00
Nicolas Meienberger
b155f82575 chore: update dependencies 2025-12-06 10:36:25 +01:00
Nico
9120153375 feat: mirror repositories (#95)
* feat: mirror repositories

feat: mirror backup repositories

* chore: pr feedbacks
2025-12-06 10:36:17 +01:00
Jakub Trávník
780fdae63e cryptoUtils.decrypt alligned with encrypt in regards of handling extra space in password file 2025-12-06 10:35:55 +01:00
Jakub Trávník
fc482e9729 crypto.Utils comments updated to reflect behaviour 2025-12-06 10:35:55 +01:00
Jakub Trávník
1fe026a76f avoid logging secrets in smb backend 2025-12-06 10:35:55 +01:00
Jakub Trávník
2df1fa53a0 feat: implement encryption for sensitive fields in volume backends 2025-12-06 10:35:55 +01:00
Nicolas Meienberger
da8e9c4ada ci: fix docker cache args on wrong step 2025-12-06 10:35:55 +01:00
Jakub Trávník
4403e3cf19 Merge branch 'main' into missing-icons 2025-12-05 00:22:39 +01:00
Jakub Trávník
ff4c1404a6 feat: add icons to buttons and alerts for improved UI clarity 2025-12-03 17:31:44 +01:00
22 changed files with 93 additions and 31 deletions

View File

@@ -2,6 +2,7 @@ import { arktypeResolver } from "@hookform/resolvers/arktype";
import { type } from "arktype"; import { type } from "arktype";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Check, Pencil, Save, X } from "lucide-react";
import { cn, slugify } from "~/client/lib/utils"; import { cn, slugify } from "~/client/lib/utils";
import { deepClean } from "~/utils/object"; import { deepClean } from "~/utils/object";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
@@ -267,6 +268,7 @@ export const CreateRepositoryForm = ({
{form.watch("path") || "/var/lib/zerobyte/repositories"} {form.watch("path") || "/var/lib/zerobyte/repositories"}
</div> </div>
<Button type="button" variant="outline" onClick={() => setShowPathWarning(true)} size="sm"> <Button type="button" variant="outline" onClick={() => setShowPathWarning(true)} size="sm">
<Pencil className="h-4 w-4 mr-2" />
Change Change
</Button> </Button>
</div> </div>
@@ -278,7 +280,7 @@ export const CreateRepositoryForm = ({
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2"> <AlertDialogTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-yellow-500" /> <AlertTriangle className="h-5 w-5 text-yellow-500" />
Important: Host Mount Required Important: Host mount required
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription className="space-y-3"> <AlertDialogDescription className="space-y-3">
<p>When selecting a custom path, ensure it is mounted from the host machine into the container.</p> <p>When selecting a custom path, ensure it is mounted from the host machine into the container.</p>
@@ -320,8 +322,14 @@ export const CreateRepositoryForm = ({
/> />
</div> </div>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>
<AlertDialogAction onClick={() => setShowPathBrowser(false)}>Done</AlertDialogAction> <X className="h-4 w-4 mr-2" />
Cancel
</AlertDialogCancel>
<AlertDialogAction onClick={() => setShowPathBrowser(false)}>
<Check className="h-4 w-4 mr-2" />
Done
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@@ -775,6 +783,7 @@ export const CreateRepositoryForm = ({
{mode === "update" && ( {mode === "update" && (
<Button type="submit" className="w-full" loading={loading}> <Button type="submit" className="w-full" loading={loading}>
<Save className="h-4 w-4 mr-2" />
Save Changes Save Changes
</Button> </Button>
)} )}

View File

@@ -1,7 +1,7 @@
import { arktypeResolver } from "@hookform/resolvers/arktype"; import { arktypeResolver } from "@hookform/resolvers/arktype";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { type } from "arktype"; import { type } from "arktype";
import { CheckCircle, Loader2, XCircle } from "lucide-react"; import { CheckCircle, Loader2, Pencil, Plug, Save, XCircle } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { cn, slugify } from "~/client/lib/utils"; import { cn, slugify } from "~/client/lib/utils";
@@ -152,6 +152,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
<div className="text-sm font-mono break-all">{field.value}</div> <div className="text-sm font-mono break-all">{field.value}</div>
</div> </div>
<Button type="button" variant="outline" size="sm" onClick={() => field.onChange("")}> <Button type="button" variant="outline" size="sm" onClick={() => field.onChange("")}>
<Pencil className="h-4 w-4 mr-2" />
Change Change
</Button> </Button>
</div> </div>
@@ -561,6 +562,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
{!testBackendConnection.isPending && testMessage && !testMessage.success && ( {!testBackendConnection.isPending && testMessage && !testMessage.success && (
<XCircle className="mr-2 h-4 w-4 text-red-500" /> <XCircle className="mr-2 h-4 w-4 text-red-500" />
)} )}
{!testBackendConnection.isPending && !testMessage && <Plug className="mr-2 h-4 w-4" />}
{testBackendConnection.isPending {testBackendConnection.isPending
? "Testing..." ? "Testing..."
: testMessage : testMessage
@@ -584,6 +586,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
)} )}
{mode === "update" && ( {mode === "update" && (
<Button type="submit" className="w-full" loading={loading}> <Button type="submit" className="w-full" loading={loading}>
<Save className="h-4 w-4 mr-2" />
Save Changes Save Changes
</Button> </Button>
)} )}

View File

@@ -164,6 +164,7 @@ export function RestoreForm({ snapshot, repositoryName, snapshotId, returnPath }
Cancel Cancel
</Button> </Button>
<Button variant="primary" onClick={handleRestore} disabled={isRestoring || !canRestore}> <Button variant="primary" onClick={handleRestore} disabled={isRestoring || !canRestore}>
<RotateCcw className="h-4 w-4 mr-2" />
{isRestoring {isRestoring
? "Restoring..." ? "Restoring..."
: selectedPaths.size > 0 : selectedPaths.size > 0

View File

@@ -1,4 +1,4 @@
import { Database, Eraser, HardDrive, Pencil, Play, Square, Trash2 } from "lucide-react"; import { Check, Database, Eraser, HardDrive, Pencil, Play, Square, Trash2, X } from "lucide-react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { OnOff } from "~/client/components/onoff"; import { OnOff } from "~/client/components/onoff";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
@@ -228,8 +228,14 @@ export const ScheduleSummary = (props: Props) => {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<div className="flex gap-3 justify-end"> <div className="flex gap-3 justify-end">
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmForget}>Run cleanup</AlertDialogAction> <X className="h-4 w-4 mr-2" />
Cancel
</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmForget}>
<Check className="h-4 w-4 mr-2" />
Run cleanup
</AlertDialogAction>
</div> </div>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>

View File

@@ -1,6 +1,6 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { FileIcon } from "lucide-react"; import { FileIcon, RotateCcw, Trash2 } from "lucide-react";
import { Link } from "react-router"; import { Link } from "react-router";
import { FileTree } from "~/client/components/file-tree"; import { FileTree } from "~/client/components/file-tree";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
@@ -98,6 +98,7 @@ export const SnapshotFileBrowser = (props: Props) => {
} }
className={buttonVariants({ variant: "primary", size: "sm" })} className={buttonVariants({ variant: "primary", size: "sm" })}
> >
<RotateCcw className="h-4 w-4" />
Restore Restore
</Link> </Link>
{onDeleteSnapshot && ( {onDeleteSnapshot && (
@@ -108,6 +109,7 @@ export const SnapshotFileBrowser = (props: Props) => {
disabled={isDeletingSnapshot} disabled={isDeletingSnapshot}
loading={isDeletingSnapshot} loading={isDeletingSnapshot}
> >
<Trash2 className="h-4 w-4 mr-2" />
{isDeletingSnapshot ? "Deleting..." : "Delete Snapshot"} {isDeletingSnapshot ? "Deleting..." : "Delete Snapshot"}
</Button> </Button>
)} )}

View File

@@ -2,6 +2,7 @@ import { useId, useState } from "react";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { redirect, useNavigate } from "react-router"; import { redirect, useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { Save, X } from "lucide-react";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { import {
AlertDialog, AlertDialog,
@@ -206,9 +207,11 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
<CreateScheduleForm volume={schedule.volume} initialValues={schedule} onSubmit={handleSubmit} formId={formId} /> <CreateScheduleForm volume={schedule.volume} initialValues={schedule} onSubmit={handleSubmit} formId={formId} />
<div className="flex justify-end mt-4 gap-2"> <div className="flex justify-end mt-4 gap-2">
<Button type="submit" className="ml-auto" variant="primary" form={formId} loading={updateSchedule.isPending}> <Button type="submit" className="ml-auto" variant="primary" form={formId} loading={updateSchedule.isPending}>
<Save className="h-4 w-4 mr-2" />
Update schedule Update schedule
</Button> </Button>
<Button variant="outline" onClick={() => setIsEditMode(false)}> <Button variant="outline" onClick={() => setIsEditMode(false)}>
<X className="h-4 w-4 mr-2" />
Cancel Cancel
</Button> </Button>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { useId, useState } from "react"; import { useId, useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
import { Database, HardDrive } from "lucide-react"; import { Database, HardDrive, Plus } from "lucide-react";
import { Link, useNavigate } from "react-router"; import { Link, useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
@@ -162,6 +162,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
<CreateScheduleForm volume={selectedVolume} onSubmit={handleSubmit} formId={formId} /> <CreateScheduleForm volume={selectedVolume} onSubmit={handleSubmit} formId={formId} />
<div className="flex justify-end mt-4 gap-2"> <div className="flex justify-end mt-4 gap-2">
<Button type="submit" variant="primary" form={formId} loading={createSchedule.isPending}> <Button type="submit" variant="primary" form={formId} loading={createSchedule.isPending}>
<Plus className="h-4 w-4 mr-2" />
Create Create
</Button> </Button>
</div> </div>

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { Bell } from "lucide-react"; import { Bell, Plus } from "lucide-react";
import { useId } from "react"; import { useId } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -68,6 +68,7 @@ export default function CreateNotification() {
Cancel Cancel
</Button> </Button>
<Button type="submit" form={formId} loading={createNotification.isPending}> <Button type="submit" form={formId} loading={createNotification.isPending}>
<Plus className="h-4 w-4 mr-2" />
Create Destination Create Destination
</Button> </Button>
</div> </div>

View File

@@ -24,7 +24,7 @@ import { getNotificationDestination } from "~/client/api-client/sdk.gen";
import type { Route } from "./+types/notification-details"; import type { Route } from "./+types/notification-details";
import { cn } from "~/client/lib/utils"; import { cn } from "~/client/lib/utils";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
import { Bell, TestTube2 } from "lucide-react"; import { Bell, Save, TestTube2, Trash2, X } from "lucide-react";
import { Alert, AlertDescription } from "~/client/components/ui/alert"; import { Alert, AlertDescription } from "~/client/components/ui/alert";
import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form"; import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form";
@@ -147,6 +147,7 @@ export default function NotificationDetailsPage({ loaderData }: Route.ComponentP
variant="destructive" variant="destructive"
loading={deleteDestination.isPending} loading={deleteDestination.isPending}
> >
<Trash2 className="h-4 w-4 mr-2" />
Delete Delete
</Button> </Button>
</div> </div>
@@ -174,6 +175,7 @@ export default function NotificationDetailsPage({ loaderData }: Route.ComponentP
<CreateNotificationForm mode="update" formId={formId} onSubmit={handleSubmit} initialValues={data.config} /> <CreateNotificationForm mode="update" formId={formId} onSubmit={handleSubmit} initialValues={data.config} />
<div className="flex justify-end gap-2 pt-4 border-t"> <div className="flex justify-end gap-2 pt-4 border-t">
<Button type="submit" form={formId} loading={updateDestination.isPending}> <Button type="submit" form={formId} loading={updateDestination.isPending}>
<Save className="h-4 w-4 mr-2" />
Save Changes Save Changes
</Button> </Button>
</div> </div>
@@ -190,8 +192,14 @@ export default function NotificationDetailsPage({ loaderData }: Route.ComponentP
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmDelete}>Delete</AlertDialogAction> <X className="h-4 w-4 mr-2" />
Cancel
</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmDelete}>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { Database } from "lucide-react"; import { Database, Plus } from "lucide-react";
import { useId } from "react"; import { useId } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -79,7 +79,8 @@ export default function CreateRepository() {
Cancel Cancel
</Button> </Button>
<Button type="submit" form={formId} loading={createRepository.isPending}> <Button type="submit" form={formId} loading={createRepository.isPending}>
Create Repository <Plus className="h-4 w-4 mr-2" />
Create repository
</Button> </Button>
</div> </div>
</CardContent> </CardContent>

View File

@@ -72,7 +72,7 @@ export default function Repositories({ loaderData }: Route.ComponentProps) {
button={ button={
<Button onClick={() => navigate("/repositories/create")}> <Button onClick={() => navigate("/repositories/create")}>
<Plus size={16} className="mr-2" /> <Plus size={16} className="mr-2" />
Create Repository Create repository
</Button> </Button>
} }
/> />

View File

@@ -25,7 +25,7 @@ import { cn } from "~/client/lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
import { RepositoryInfoTabContent } from "../tabs/info"; import { RepositoryInfoTabContent } from "../tabs/info";
import { RepositorySnapshotsTabContent } from "../tabs/snapshots"; import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
import { Loader2 } from "lucide-react"; import { Loader2, Stethoscope, Trash2, X } from "lucide-react";
export const handle = { export const handle = {
breadcrumb: (match: Route.MetaArgs) => [ breadcrumb: (match: Route.MetaArgs) => [
@@ -149,13 +149,17 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro
{doctorMutation.isPending ? ( {doctorMutation.isPending ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
Running Doctor... Running doctor...
</> </>
) : ( ) : (
"Run Doctor" <>
<Stethoscope className="h-4 w-4 mr-2" />
Run doctor
</>
)} )}
</Button> </Button>
<Button variant="destructive" onClick={() => setShowDeleteConfirm(true)} disabled={deleteRepo.isPending}> <Button variant="destructive" onClick={() => setShowDeleteConfirm(true)} disabled={deleteRepo.isPending}>
<Trash2 className="h-4 w-4 mr-2" />
Delete Delete
</Button> </Button>
</div> </div>
@@ -184,11 +188,15 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<div className="flex gap-3 justify-end"> <div className="flex gap-3 justify-end">
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>
<X className="h-4 w-4 mr-2" />
Cancel
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={handleConfirmDelete} onClick={handleConfirmDelete}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90" className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
> >
<Trash2 className="h-4 w-4 mr-2" />
Delete repository Delete repository
</AlertDialogAction> </AlertDialogAction>
</div> </div>
@@ -198,7 +206,7 @@ export default function RepositoryDetailsPage({ loaderData }: Route.ComponentPro
<AlertDialog open={showDoctorResults} onOpenChange={setShowDoctorResults}> <AlertDialog open={showDoctorResults} onOpenChange={setShowDoctorResults}>
<AlertDialogContent className="max-w-2xl"> <AlertDialogContent className="max-w-2xl">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Doctor Results</AlertDialogTitle> <AlertDialogTitle>Doctor results</AlertDialogTitle>
<AlertDialogDescription>Repository doctor operation completed</AlertDialogDescription> <AlertDialogDescription>Repository doctor operation completed</AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>

View File

@@ -2,6 +2,7 @@ import { useMutation } from "@tanstack/react-query";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { Check, Save } from "lucide-react";
import { Card } from "~/client/components/ui/card"; import { Card } from "~/client/components/ui/card";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { Input } from "~/client/components/ui/input"; import { Input } from "~/client/components/ui/input";
@@ -146,6 +147,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
<div className="flex justify-end pt-4 border-t"> <div className="flex justify-end pt-4 border-t">
<Button type="submit" disabled={!hasChanges || updateMutation.isPending} loading={updateMutation.isPending}> <Button type="submit" disabled={!hasChanges || updateMutation.isPending} loading={updateMutation.isPending}>
<Save className="h-4 w-4 mr-2" />
Save Changes Save Changes
</Button> </Button>
</div> </div>
@@ -155,12 +157,15 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}> <AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Update Repository</AlertDialogTitle> <AlertDialogTitle>Update repository</AlertDialogTitle>
<AlertDialogDescription>Are you sure you want to update the repository settings?</AlertDialogDescription> <AlertDialogDescription>Are you sure you want to update the repository settings?</AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={confirmUpdate}>Update</AlertDialogAction> <AlertDialogAction onClick={confirmUpdate}>
<Check className="h-4 w-4" />
Update
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Database } from "lucide-react"; import { Database, X } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { listBackupSchedulesOptions, listSnapshotsOptions } from "~/client/api-client/@tanstack/react-query.gen"; import { listBackupSchedulesOptions, listSnapshotsOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { SnapshotsTable } from "~/client/components/snapshots-table"; import { SnapshotsTable } from "~/client/components/snapshots-table";
@@ -128,6 +128,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<p className="text-muted-foreground">No snapshots match your search.</p> <p className="text-muted-foreground">No snapshots match your search.</p>
<Button onClick={() => setSearchQuery("")} variant="outline" size="sm"> <Button onClick={() => setSearchQuery("")} variant="outline" size="sm">
<X className="h-4 w-4 mr-2" />
Clear search Clear search
</Button> </Button>
</div> </div>

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { Download, KeyRound, User } from "lucide-react"; import { Download, KeyRound, User, X } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -195,6 +195,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
/> />
</div> </div>
<Button type="submit" loading={changePassword.isPending} className="mt-4"> <Button type="submit" loading={changePassword.isPending} className="mt-4">
<KeyRound className="h-4 w-4 mr-2" />
Change Password Change Password
</Button> </Button>
</form> </form>
@@ -252,9 +253,11 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
setDownloadPassword(""); setDownloadPassword("");
}} }}
> >
<X className="h-4 w-4 mr-2" />
Cancel Cancel
</Button> </Button>
<Button type="submit" loading={downloadResticPassword.isPending}> <Button type="submit" loading={downloadResticPassword.isPending}>
<Download className="h-4 w-4 mr-2" />
Download Download
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { formatDistanceToNow } from "date-fns"; import { formatDistanceToNow } from "date-fns";
import { HeartIcon } from "lucide-react"; import { Activity, HeartIcon } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { healthCheckVolumeMutation, updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { healthCheckVolumeMutation, updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
import { OnOff } from "~/client/components/onoff"; import { OnOff } from "~/client/components/onoff";
@@ -80,6 +80,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
loading={healthcheck.isPending} loading={healthcheck.isPending}
onClick={() => healthcheck.mutate({ path: { name: volume.name } })} onClick={() => healthcheck.mutate({ path: { name: volume.name } })}
> >
<Activity className="h-4 w-4 mr-2" />
Run Health Check Run Health Check
</Button> </Button>
</div> </div>

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { HardDrive } from "lucide-react"; import { HardDrive, Plus } from "lucide-react";
import { useId } from "react"; import { useId } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -73,6 +73,7 @@ export default function CreateVolume() {
Cancel Cancel
</Button> </Button>
<Button type="submit" form={formId} loading={createVolume.isPending}> <Button type="submit" form={formId} loading={createVolume.isPending}>
<Plus className="h-4 w-4 mr-2" />
Create Volume Create Volume
</Button> </Button>
</div> </div>

View File

@@ -2,6 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { useNavigate, useParams, useSearchParams } from "react-router"; import { useNavigate, useParams, useSearchParams } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { useState } from "react"; import { useState } from "react";
import { Plug, Unplug } from "lucide-react";
import { StatusDot } from "~/client/components/status-dot"; import { StatusDot } from "~/client/components/status-dot";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
@@ -148,6 +149,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
loading={mountVol.isPending} loading={mountVol.isPending}
className={cn({ hidden: volume.status === "mounted" })} className={cn({ hidden: volume.status === "mounted" })}
> >
<Plug className="h-4 w-4 mr-2" />
Mount Mount
</Button> </Button>
<Button <Button
@@ -156,6 +158,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
loading={unmountVol.isPending} loading={unmountVol.isPending}
className={cn({ hidden: volume.status !== "mounted" })} className={cn({ hidden: volume.status !== "mounted" })}
> >
<Unplug className="h-4 w-4 mr-2" />
Unmount Unmount
</Button> </Button>
<Button variant="destructive" onClick={() => setShowDeleteConfirm(true)} disabled={deleteVol.isPending}> <Button variant="destructive" onClick={() => setShowDeleteConfirm(true)} disabled={deleteVol.isPending}>

View File

@@ -2,6 +2,7 @@ import { useMutation } from "@tanstack/react-query";
import { useState } from "react"; import { useState } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { Check } from "lucide-react";
import { CreateVolumeForm, type FormValues } from "~/client/components/create-volume-form"; import { CreateVolumeForm, type FormValues } from "~/client/components/create-volume-form";
import { import {
AlertDialog, AlertDialog,
@@ -94,7 +95,10 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={confirmUpdate}>Update</AlertDialogAction> <AlertDialogAction onClick={confirmUpdate}>
<Check className="h-4 w-4" />
Update
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>

View File

@@ -795,7 +795,7 @@ const cleanupTemporaryKeys = async (config: RepositoryConfig, env: Record<string
}; };
const addCommonArgs = (args: string[], env: Record<string, string>) => { const addCommonArgs = (args: string[], env: Record<string, string>) => {
args.push("--retry-lock", "1m", "--json"); args.push("--json");
if (env._SFTP_SSH_ARGS) { if (env._SFTP_SSH_ARGS) {
args.push("-o", `sftp.args=${env._SFTP_SSH_ARGS}`); args.push("-o", `sftp.args=${env._SFTP_SSH_ARGS}`);

View File

@@ -1,8 +1,9 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json", "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
"defaultBranch": "origin/main",
"useIgnoreFile": true "useIgnoreFile": true
}, },
"files": { "files": {

View File

@@ -9,7 +9,7 @@
"start": "bun ./dist/server/index.js", "start": "bun ./dist/server/index.js",
"tsc": "react-router typegen && tsc", "tsc": "react-router typegen && tsc",
"lint": "biome check .", "lint": "biome check .",
"lint:ci": "biome check . --ci", "lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched",
"start:dev": "docker compose down && docker compose up --build zerobyte-dev", "start:dev": "docker compose down && docker compose up --build zerobyte-dev",
"start:prod": "docker compose down && docker compose up --build zerobyte-prod", "start:prod": "docker compose down && docker compose up --build zerobyte-prod",
"gen:api-client": "openapi-ts", "gen:api-client": "openapi-ts",