refactor(navigation): use volume id in urls instead of name
This commit is contained in:
parent
e59a3eac2f
commit
23acae1121
23 changed files with 2461 additions and 109 deletions
|
|
@ -1260,7 +1260,7 @@ export const getUpdatesOptions = (options?: Options<GetUpdatesData>) =>
|
|||
});
|
||||
|
||||
/**
|
||||
* Download the Restic password file for backup recovery. Requires password re-authentication.
|
||||
* Download the organization's Restic password for backup recovery. Requires organization owner or admin role and password re-authentication.
|
||||
*/
|
||||
export const downloadResticPasswordMutation = (
|
||||
options?: Partial<Options<DownloadResticPasswordData>>,
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ export const testConnection = <ThrowOnError extends boolean = false>(
|
|||
*/
|
||||
export const deleteVolume = <ThrowOnError extends boolean = false>(options: Options<DeleteVolumeData, ThrowOnError>) =>
|
||||
(options.client ?? client).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}",
|
||||
url: "/api/v1/volumes/{id}",
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ export const deleteVolume = <ThrowOnError extends boolean = false>(options: Opti
|
|||
*/
|
||||
export const getVolume = <ThrowOnError extends boolean = false>(options: Options<GetVolumeData, ThrowOnError>) =>
|
||||
(options.client ?? client).get<GetVolumeResponses, GetVolumeErrors, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}",
|
||||
url: "/api/v1/volumes/{id}",
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ export const getVolume = <ThrowOnError extends boolean = false>(options: Options
|
|||
*/
|
||||
export const updateVolume = <ThrowOnError extends boolean = false>(options: Options<UpdateVolumeData, ThrowOnError>) =>
|
||||
(options.client ?? client).put<UpdateVolumeResponses, UpdateVolumeErrors, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}",
|
||||
url: "/api/v1/volumes/{id}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -210,7 +210,7 @@ export const updateVolume = <ThrowOnError extends boolean = false>(options: Opti
|
|||
*/
|
||||
export const mountVolume = <ThrowOnError extends boolean = false>(options: Options<MountVolumeData, ThrowOnError>) =>
|
||||
(options.client ?? client).post<MountVolumeResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}/mount",
|
||||
url: "/api/v1/volumes/{id}/mount",
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ export const unmountVolume = <ThrowOnError extends boolean = false>(
|
|||
options: Options<UnmountVolumeData, ThrowOnError>,
|
||||
) =>
|
||||
(options.client ?? client).post<UnmountVolumeResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}/unmount",
|
||||
url: "/api/v1/volumes/{id}/unmount",
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -232,7 +232,7 @@ export const healthCheckVolume = <ThrowOnError extends boolean = false>(
|
|||
options: Options<HealthCheckVolumeData, ThrowOnError>,
|
||||
) =>
|
||||
(options.client ?? client).post<HealthCheckVolumeResponses, HealthCheckVolumeErrors, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}/health-check",
|
||||
url: "/api/v1/volumes/{id}/health-check",
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ export const healthCheckVolume = <ThrowOnError extends boolean = false>(
|
|||
*/
|
||||
export const listFiles = <ThrowOnError extends boolean = false>(options: Options<ListFilesData, ThrowOnError>) =>
|
||||
(options.client ?? client).get<ListFilesResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}/files",
|
||||
url: "/api/v1/volumes/{id}/files",
|
||||
...options,
|
||||
});
|
||||
|
||||
|
|
@ -708,7 +708,7 @@ export const getUpdates = <ThrowOnError extends boolean = false>(options?: Optio
|
|||
});
|
||||
|
||||
/**
|
||||
* Download the Restic password file for backup recovery. Requires password re-authentication.
|
||||
* Download the organization's Restic password for backup recovery. Requires organization owner or admin role and password re-authentication.
|
||||
*/
|
||||
export const downloadResticPassword = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<DownloadResticPasswordData, ThrowOnError>,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ import { useFileBrowser } from "../hooks/use-file-browser";
|
|||
import { parseError } from "../lib/errors";
|
||||
|
||||
type VolumeFileBrowserProps = {
|
||||
volumeName: string;
|
||||
volumeId: string;
|
||||
enabled?: boolean;
|
||||
withCheckboxes?: boolean;
|
||||
selectedPaths?: Set<string>;
|
||||
|
|
@ -18,7 +18,7 @@ type VolumeFileBrowserProps = {
|
|||
};
|
||||
|
||||
export const VolumeFileBrowser = ({
|
||||
volumeName,
|
||||
volumeId,
|
||||
enabled = true,
|
||||
withCheckboxes = false,
|
||||
selectedPaths,
|
||||
|
|
@ -31,7 +31,7 @@ export const VolumeFileBrowser = ({
|
|||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, isLoading, error } = useQuery({
|
||||
...listFilesOptions({ path: { name: volumeName } }),
|
||||
...listFilesOptions({ path: { id: volumeId } }),
|
||||
enabled,
|
||||
});
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ export const VolumeFileBrowser = ({
|
|||
fetchFolder: async (path) => {
|
||||
return await queryClient.ensureQueryData(
|
||||
listFilesOptions({
|
||||
path: { name: volumeName },
|
||||
path: { id: volumeId },
|
||||
query: { path },
|
||||
}),
|
||||
);
|
||||
|
|
@ -49,7 +49,7 @@ export const VolumeFileBrowser = ({
|
|||
prefetchFolder: (path) => {
|
||||
void queryClient.prefetchQuery(
|
||||
listFilesOptions({
|
||||
path: { name: volumeName },
|
||||
path: { id: volumeId },
|
||||
query: { path },
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
|||
<CardContent>
|
||||
<VolumeFileBrowser
|
||||
key={volume.id}
|
||||
volumeName={volume.name}
|
||||
volumeId={volume.shortId}
|
||||
selectedPaths={selectedPaths}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
withCheckboxes={true}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export const ScheduleSummary = (props: Props) => {
|
|||
<div>
|
||||
<CardTitle>{schedule.name}</CardTitle>
|
||||
<CardDescription className="mt-1">
|
||||
<Link to={`/volumes/${schedule.volume.name}`} className="hover:underline">
|
||||
<Link to={`/volumes/${schedule.volume.shortId}`} className="hover:underline">
|
||||
<HardDrive className="inline h-4 w-4 mr-2" />
|
||||
<span>{schedule.volume.name}</span>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps
|
|||
<span className="text-muted-foreground">Volume:</span>
|
||||
<p>
|
||||
<Link
|
||||
to={`/volumes/${backupSchedule?.volume.name}`}
|
||||
to={`/volumes/${backupSchedule?.volume.shortId}`}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{backupSchedule?.volume.name}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
|
|||
<OnOff
|
||||
isOn={volume.autoRemount}
|
||||
toggle={() =>
|
||||
toggleAutoRemount.mutate({ path: { name: volume.name }, body: { autoRemount: !volume.autoRemount } })
|
||||
toggleAutoRemount.mutate({ path: { id: volume.shortId }, body: { autoRemount: !volume.autoRemount } })
|
||||
}
|
||||
disabled={toggleAutoRemount.isPending}
|
||||
enabledLabel="Enabled"
|
||||
|
|
@ -74,7 +74,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
|
|||
variant="outline"
|
||||
className="mt-4"
|
||||
loading={healthcheck.isPending}
|
||||
onClick={() => healthcheck.mutate({ path: { name: volume.name } })}
|
||||
onClick={() => healthcheck.mutate({ path: { id: volume.shortId } })}
|
||||
>
|
||||
<Activity className="h-4 w-4 mr-2" />
|
||||
Run Health Check
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function CreateVolume() {
|
|||
...createVolumeMutation(),
|
||||
onSuccess: (data) => {
|
||||
toast.success("Volume created successfully");
|
||||
void navigate(`/volumes/${data.name}`);
|
||||
void navigate(`/volumes/${data.shortId}`);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | "
|
|||
};
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: (match: Route.MetaArgs) => [{ label: "Volumes", href: "/volumes" }, { label: match.params.name }],
|
||||
breadcrumb: (match: Route.MetaArgs) => [{ label: "Volumes", href: "/volumes" }, { label: match.params.id }],
|
||||
};
|
||||
|
||||
export function meta({ params }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: `Zerobyte - ${params.name}` },
|
||||
{ title: `Zerobyte - ${params.id}` },
|
||||
{
|
||||
name: "description",
|
||||
content: "View and manage volume details, configuration, and files.",
|
||||
|
|
@ -55,19 +55,19 @@ export function meta({ params }: Route.MetaArgs) {
|
|||
}
|
||||
|
||||
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
|
||||
const volume = await getVolume({ path: { name: params.name } });
|
||||
const volume = await getVolume({ path: { id: params.id } });
|
||||
if (volume.data) return volume.data;
|
||||
};
|
||||
|
||||
export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
|
||||
const { name } = useParams<{ name: string }>();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const activeTab = searchParams.get("tab") || "info";
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
||||
const { data } = useQuery({
|
||||
...getVolumeOptions({ path: { name: name ?? "" } }),
|
||||
...getVolumeOptions({ path: { id: id ?? "" } }),
|
||||
initialData: loaderData,
|
||||
});
|
||||
|
||||
|
|
@ -110,10 +110,10 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
|
|||
|
||||
const handleConfirmDelete = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
deleteVol.mutate({ path: { name: name ?? "" } });
|
||||
deleteVol.mutate({ path: { id: id ?? "" } });
|
||||
};
|
||||
|
||||
if (!name) {
|
||||
if (!id) {
|
||||
return <div>Volume not found</div>;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
|
|||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
onClick={() => mountVol.mutate({ path: { name } })}
|
||||
onClick={() => mountVol.mutate({ path: { id } })}
|
||||
loading={mountVol.isPending}
|
||||
className={cn({ hidden: volume.status === "mounted" })}
|
||||
>
|
||||
|
|
@ -148,7 +148,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
|
|||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => unmountVol.mutate({ path: { name } })}
|
||||
onClick={() => unmountVol.mutate({ path: { id } })}
|
||||
loading={unmountVol.isPending}
|
||||
className={cn({ hidden: volume.status !== "mounted" })}
|
||||
>
|
||||
|
|
@ -178,7 +178,7 @@ export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
|
|||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete volume?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete the volume <strong>{name}</strong>? This action cannot be undone.
|
||||
Are you sure you want to delete the volume <strong>{volume.name}</strong>? This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="flex gap-3 justify-end">
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ export default function Volumes({ loaderData }: Route.ComponentProps) {
|
|||
<TableRow
|
||||
key={volume.name}
|
||||
className="hover:bg-accent/50 hover:cursor-pointer"
|
||||
onClick={() => navigate(`/volumes/${volume.name}`)}
|
||||
onClick={() => navigate(`/volumes/${volume.shortId}`)}
|
||||
>
|
||||
<TableCell className="font-medium text-strong-accent">{volume.name}</TableCell>
|
||||
<TableCell>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const FilesTabContent = ({ volume }: Props) => {
|
|||
</CardHeader>
|
||||
<CardContent className="flex-1 overflow-hidden flex flex-col">
|
||||
<VolumeFileBrowser
|
||||
volumeName={volume.name}
|
||||
volumeId={volume.shortId}
|
||||
enabled={volume.status === "mounted"}
|
||||
className="overflow-auto flex-1 border rounded-md bg-card p-2"
|
||||
emptyMessage="This volume is empty."
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
|||
setPendingValues(null);
|
||||
|
||||
if (data.name !== volume.name) {
|
||||
void navigate(`/volumes/${data.name}`);
|
||||
void navigate(`/volumes/${data.shortId}`);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
@ -58,7 +58,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
|||
const confirmUpdate = () => {
|
||||
if (pendingValues) {
|
||||
updateMutation.mutate({
|
||||
path: { name: volume.name },
|
||||
path: { id: volume.shortId },
|
||||
body: { name: pendingValues.name, config: pendingValues },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
1
app/drizzle/0042_watery_liz_osborn.sql
Normal file
1
app/drizzle/0042_watery_liz_osborn.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
CREATE UNIQUE INDEX `volumes_table_name_organization_id_unique` ON `volumes_table` (`name`,`organization_id`);
|
||||
1628
app/drizzle/meta/0042_snapshot.json
Normal file
1628
app/drizzle/meta/0042_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -295,6 +295,13 @@
|
|||
"when": 1768684588784,
|
||||
"tag": "0041_motionless_storm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 42,
|
||||
"version": "6",
|
||||
"when": 1768687163221,
|
||||
"tag": "0042_watery_liz_osborn",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ export default [
|
|||
route("/", "./client/routes/root.tsx"),
|
||||
route("volumes", "./client/modules/volumes/routes/volumes.tsx"),
|
||||
route("volumes/create", "./client/modules/volumes/routes/create-volume.tsx"),
|
||||
route("volumes/:name", "./client/modules/volumes/routes/volume-details.tsx"),
|
||||
route("volumes/:id", "./client/modules/volumes/routes/volume-details.tsx"),
|
||||
route("backups", "./client/modules/backups/routes/backups.tsx"),
|
||||
route("backups/create", "./client/modules/backups/routes/create-backup.tsx"),
|
||||
route("backups/:id", "./client/modules/backups/routes/backup-details.tsx"),
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export const volumesTable = sqliteTable("volumes_table", {
|
|||
organizationId: text("organization_id")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
});
|
||||
}, (table) => [unique().on(table.name, table.organizationId)]);
|
||||
export type Volume = typeof volumesTable.$inferSelect;
|
||||
export type VolumeInsert = typeof volumesTable.$inferInsert;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class VolumeAutoRemountJob extends Job {
|
|||
for (const volume of volumes) {
|
||||
if (volume.autoRemount) {
|
||||
try {
|
||||
await volumeService.mountVolume(volume.name, volume.organizationId);
|
||||
await volumeService.mountVolume(volume.id, volume.organizationId);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to auto-remount volume ${volume.name}:`, err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ export class VolumeHealthCheckJob extends Job {
|
|||
});
|
||||
|
||||
for (const volume of volumes) {
|
||||
const { status } = await volumeService.checkHealth(volume.name, volume.organizationId);
|
||||
const { status } = await volumeService.checkHealth(volume.id, volume.organizationId);
|
||||
if (status === "error" && volume.autoRemount) {
|
||||
await volumeService.mountVolume(volume.name, volume.organizationId);
|
||||
await volumeService.mountVolume(volume.id, volume.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const ensureLatestConfigurationSchema = async () => {
|
|||
const volumes = await db.query.volumesTable.findMany({});
|
||||
|
||||
for (const volume of volumes) {
|
||||
await volumeService.updateVolume(volume.name, volume, volume.organizationId).catch((err) => {
|
||||
await volumeService.updateVolume(volume.id, volume, volume.organizationId).catch((err) => {
|
||||
logger.error(`Failed to update volume ${volume.name}: ${err}`);
|
||||
});
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ export const startup = async () => {
|
|||
});
|
||||
|
||||
for (const volume of volumes) {
|
||||
await volumeService.mountVolume(volume.name, volume.organizationId).catch((err) => {
|
||||
await volumeService.mountVolume(volume.id, volume.organizationId).catch((err) => {
|
||||
logger.error(`Error auto-remounting volume ${volume.name} on startup: ${err.message}`);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,15 +50,15 @@ export const volumeController = new Hono()
|
|||
|
||||
return c.json(result, 200);
|
||||
})
|
||||
.delete("/:name", deleteVolumeDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
await volumeService.deleteVolume(name, c.get("organizationId"));
|
||||
.delete("/:id", deleteVolumeDto, async (c) => {
|
||||
const { id } = c.req.param();
|
||||
await volumeService.deleteVolume(id, c.get("organizationId"));
|
||||
|
||||
return c.json({ message: "Volume deleted" }, 200);
|
||||
})
|
||||
.get("/:name", getVolumeDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const res = await volumeService.getVolume(name, c.get("organizationId"));
|
||||
.get("/:id", getVolumeDto, async (c) => {
|
||||
const { id } = c.req.param();
|
||||
const res = await volumeService.getVolume(id, c.get("organizationId"));
|
||||
|
||||
const response = {
|
||||
volume: {
|
||||
|
|
@ -74,10 +74,10 @@ export const volumeController = new Hono()
|
|||
|
||||
return c.json<GetVolumeDto>(response, 200);
|
||||
})
|
||||
.put("/:name", updateVolumeDto, validator("json", updateVolumeBody), async (c) => {
|
||||
const { name } = c.req.param();
|
||||
.put("/:id", updateVolumeDto, validator("json", updateVolumeBody), async (c) => {
|
||||
const { id } = c.req.param();
|
||||
const body = c.req.valid("json");
|
||||
const res = await volumeService.updateVolume(name, body, c.get("organizationId"));
|
||||
const res = await volumeService.updateVolume(id, body, c.get("organizationId"));
|
||||
|
||||
const response = {
|
||||
...res.volume,
|
||||
|
|
@ -86,28 +86,28 @@ export const volumeController = new Hono()
|
|||
|
||||
return c.json<UpdateVolumeDto>(response, 200);
|
||||
})
|
||||
.post("/:name/mount", mountVolumeDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const { error, status } = await volumeService.mountVolume(name, c.get("organizationId"));
|
||||
.post("/:id/mount", mountVolumeDto, async (c) => {
|
||||
const { id } = c.req.param();
|
||||
const { error, status } = await volumeService.mountVolume(id, c.get("organizationId"));
|
||||
|
||||
return c.json({ error, status }, error ? 500 : 200);
|
||||
})
|
||||
.post("/:name/unmount", unmountVolumeDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const { error, status } = await volumeService.unmountVolume(name, c.get("organizationId"));
|
||||
.post("/:id/unmount", unmountVolumeDto, async (c) => {
|
||||
const { id } = c.req.param();
|
||||
const { error, status } = await volumeService.unmountVolume(id, c.get("organizationId"));
|
||||
|
||||
return c.json({ error, status }, error ? 500 : 200);
|
||||
})
|
||||
.post("/:name/health-check", healthCheckDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const { error, status } = await volumeService.checkHealth(name, c.get("organizationId"));
|
||||
.post("/:id/health-check", healthCheckDto, async (c) => {
|
||||
const { id } = c.req.param();
|
||||
const { error, status } = await volumeService.checkHealth(id, c.get("organizationId"));
|
||||
|
||||
return c.json({ error, status }, 200);
|
||||
})
|
||||
.get("/:name/files", listFilesDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
.get("/:id/files", listFilesDto, async (c) => {
|
||||
const { id } = c.req.param();
|
||||
const subPath = c.req.query("path");
|
||||
const result = await volumeService.listFiles(name, c.get("organizationId"), subPath);
|
||||
const result = await volumeService.listFiles(id, c.get("organizationId"), subPath);
|
||||
|
||||
const response = {
|
||||
files: result.files,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@ const listVolumes = async (organizationId: string) => {
|
|||
return volumes;
|
||||
};
|
||||
|
||||
const findVolume = async (idOrShortId: string | number, organizationId: string) => {
|
||||
const isNumeric = typeof idOrShortId === "number" || /^\d+$/.test(String(idOrShortId));
|
||||
return await db.query.volumesTable.findFirst({
|
||||
where: and(
|
||||
isNumeric ? eq(volumesTable.id, Number(idOrShortId)) : eq(volumesTable.shortId, idOrShortId),
|
||||
eq(volumesTable.organizationId, organizationId),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const createVolume = async (name: string, backendConfig: BackendConfig, organizationId: string) => {
|
||||
const slug = slugify(name, { lower: true, strict: true });
|
||||
|
||||
|
|
@ -90,10 +100,8 @@ const createVolume = async (name: string, backendConfig: BackendConfig, organiza
|
|||
return { volume: created, status: 201 };
|
||||
};
|
||||
|
||||
const deleteVolume = async (name: string, organizationId: string) => {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const deleteVolume = async (idOrShortId: string | number, organizationId: string) => {
|
||||
const volume = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!volume) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
@ -106,10 +114,8 @@ const deleteVolume = async (name: string, organizationId: string) => {
|
|||
.where(and(eq(volumesTable.id, volume.id), eq(volumesTable.organizationId, organizationId)));
|
||||
};
|
||||
|
||||
const mountVolume = async (name: string, organizationId: string) => {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const mountVolume = async (idOrShortId: string | number, organizationId: string) => {
|
||||
const volume = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!volume) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
@ -124,16 +130,14 @@ const mountVolume = async (name: string, organizationId: string) => {
|
|||
.where(and(eq(volumesTable.id, volume.id), eq(volumesTable.organizationId, organizationId)));
|
||||
|
||||
if (status === "mounted") {
|
||||
serverEvents.emit("volume:mounted", { volumeName: name });
|
||||
serverEvents.emit("volume:mounted", { volumeName: volume.name });
|
||||
}
|
||||
|
||||
return { error, status };
|
||||
};
|
||||
|
||||
const unmountVolume = async (name: string, organizationId: string) => {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const unmountVolume = async (idOrShortId: string | number, organizationId: string) => {
|
||||
const volume = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!volume) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
@ -148,16 +152,14 @@ const unmountVolume = async (name: string, organizationId: string) => {
|
|||
.where(and(eq(volumesTable.id, volume.id), eq(volumesTable.organizationId, organizationId)));
|
||||
|
||||
if (status === "unmounted") {
|
||||
serverEvents.emit("volume:unmounted", { volumeName: name });
|
||||
serverEvents.emit("volume:unmounted", { volumeName: volume.name });
|
||||
}
|
||||
|
||||
return { error, status };
|
||||
};
|
||||
|
||||
const getVolume = async (name: string, organizationId: string) => {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const getVolume = async (idOrShortId: string | number, organizationId: string) => {
|
||||
const volume = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!volume) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
@ -166,7 +168,7 @@ const getVolume = async (name: string, organizationId: string) => {
|
|||
let statfs: Partial<StatFs> = {};
|
||||
if (volume.status === "mounted") {
|
||||
statfs = await withTimeout(getStatFs(getVolumePath(volume)), 1000, "getStatFs").catch((error) => {
|
||||
logger.warn(`Failed to get statfs for volume ${name}: ${toMessage(error)}`);
|
||||
logger.warn(`Failed to get statfs for volume ${volume.name}: ${toMessage(error)}`);
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
|
@ -174,10 +176,8 @@ const getVolume = async (name: string, organizationId: string) => {
|
|||
return { volume, statfs };
|
||||
};
|
||||
|
||||
const updateVolume = async (name: string, volumeData: UpdateVolumeBody, organizationId: string) => {
|
||||
const existing = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const updateVolume = async (idOrShortId: string | number, volumeData: UpdateVolumeBody, organizationId: string) => {
|
||||
const existing = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!existing) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
@ -277,10 +277,8 @@ const testConnection = async (backendConfig: BackendConfig) => {
|
|||
};
|
||||
};
|
||||
|
||||
const checkHealth = async (name: string, organizationId: string) => {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const checkHealth = async (idOrShortId: string | number, organizationId: string) => {
|
||||
const volume = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!volume) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
@ -290,7 +288,7 @@ const checkHealth = async (name: string, organizationId: string) => {
|
|||
const { error, status } = await backend.checkHealth();
|
||||
|
||||
if (status !== volume.status) {
|
||||
serverEvents.emit("volume:status_changed", { volumeName: name, status });
|
||||
serverEvents.emit("volume:status_changed", { volumeName: volume.name, status });
|
||||
}
|
||||
|
||||
await db
|
||||
|
|
@ -301,10 +299,8 @@ const checkHealth = async (name: string, organizationId: string) => {
|
|||
return { status, error };
|
||||
};
|
||||
|
||||
const listFiles = async (name: string, organizationId: string, subPath?: string) => {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: and(eq(volumesTable.name, name), eq(volumesTable.organizationId, organizationId)),
|
||||
});
|
||||
const listFiles = async (idOrShortId: string | number, organizationId: string, subPath?: string) => {
|
||||
const volume = await findVolume(idOrShortId, organizationId);
|
||||
|
||||
if (!volume) {
|
||||
throw new NotFoundError("Volume not found");
|
||||
|
|
|
|||
Loading…
Reference in a new issue