mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
refactor: unify backend and frontend servers (#3)
* refactor: unify backend and frontend servers * refactor: correct paths for openapi & drizzle * refactor: move api-client to client * fix: drizzle paths * chore: fix linting issues * fix: form reset issue
This commit is contained in:
@@ -12,12 +12,9 @@
|
|||||||
!**/build.ts
|
!**/build.ts
|
||||||
!**/components.json
|
!**/components.json
|
||||||
|
|
||||||
!apps/**/src/**
|
!src/**
|
||||||
!apps/**/drizzle/**
|
!app/**
|
||||||
!apps/**/app/**
|
!public/**
|
||||||
!apps/**/public/**
|
|
||||||
|
|
||||||
!packages/**/src/**
|
|
||||||
|
|
||||||
# License files and attributions
|
# License files and attributions
|
||||||
!LICENSE
|
!LICENSE
|
||||||
|
|||||||
48
.gitignore
vendored
48
.gitignore
vendored
@@ -1,47 +1,11 @@
|
|||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
.DS_Store
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
/node_modules/
|
||||||
#
|
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# React Router
|
||||||
*.test
|
/.react-router/
|
||||||
|
/build/
|
||||||
|
/dist/
|
||||||
|
|
||||||
# Code coverage profiles and other test artifacts
|
|
||||||
*.out
|
|
||||||
coverage.*
|
|
||||||
*.coverprofile
|
|
||||||
profile.cov
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
||||||
# Go workspace file
|
|
||||||
go.work
|
|
||||||
go.work.sum
|
|
||||||
|
|
||||||
# env file
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# Editor/IDE
|
|
||||||
# .idea/
|
|
||||||
# .vscode/
|
|
||||||
ironmount
|
|
||||||
out/
|
|
||||||
*.db
|
|
||||||
tmp/
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
.env*
|
|
||||||
|
|
||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
mutagen.yml.lock
|
|
||||||
|
|
||||||
data/
|
|
||||||
|
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
|
|||||||
19
Dockerfile
19
Dockerfile
@@ -45,15 +45,12 @@ WORKDIR /app
|
|||||||
COPY --from=deps /deps/restic /usr/local/bin/restic
|
COPY --from=deps /deps/restic /usr/local/bin/restic
|
||||||
COPY --from=deps /deps/rclone /usr/local/bin/rclone
|
COPY --from=deps /deps/rclone /usr/local/bin/rclone
|
||||||
COPY ./package.json ./bun.lock ./
|
COPY ./package.json ./bun.lock ./
|
||||||
COPY ./packages/schemas/package.json ./packages/schemas/package.json
|
|
||||||
COPY ./apps/client/package.json ./apps/client/package.json
|
|
||||||
COPY ./apps/server/package.json ./apps/server/package.json
|
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 4096
|
||||||
|
|
||||||
CMD ["bun", "run", "dev"]
|
CMD ["bun", "run", "dev"]
|
||||||
|
|
||||||
@@ -65,13 +62,12 @@ FROM oven/bun:${BUN_VERSION} AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./package.json ./bun.lock ./
|
COPY ./package.json ./bun.lock ./
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
COPY ./packages/schemas/package.json ./packages/schemas/package.json
|
COPY ./packages/schemas/package.json ./packages/schemas/package.json
|
||||||
COPY ./apps/client/package.json ./apps/client/package.json
|
COPY ./apps/client/package.json ./apps/client/package.json
|
||||||
COPY ./apps/server/package.json ./apps/server/package.json
|
COPY ./apps/server/package.json ./apps/server/package.json
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
@@ -82,16 +78,19 @@ ENV NODE_ENV="production"
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/package.json ./
|
||||||
|
RUN bun install --production --frozen-lockfile
|
||||||
|
|
||||||
COPY --from=deps /deps/restic /usr/local/bin/restic
|
COPY --from=deps /deps/restic /usr/local/bin/restic
|
||||||
COPY --from=deps /deps/rclone /usr/local/bin/rclone
|
COPY --from=deps /deps/rclone /usr/local/bin/rclone
|
||||||
COPY --from=builder /app/apps/server/dist ./
|
COPY --from=builder /app/dist/client ./dist/client
|
||||||
COPY --from=builder /app/apps/server/drizzle ./assets/migrations
|
COPY --from=builder /app/dist/server ./dist/server
|
||||||
COPY --from=builder /app/apps/client/dist/client ./assets/frontend
|
COPY --from=builder /app/app/drizzle ./assets/migrations
|
||||||
|
|
||||||
# Include third-party licenses and attribution
|
# Include third-party licenses and attribution
|
||||||
COPY ./LICENSES ./LICENSES
|
COPY ./LICENSES ./LICENSES
|
||||||
COPY ./NOTICES.md ./NOTICES.md
|
COPY ./NOTICES.md ./NOTICES.md
|
||||||
COPY ./LICENSE ./LICENSE.md
|
COPY ./LICENSE ./LICENSE.md
|
||||||
|
|
||||||
CMD ["bun", "./index.js"]
|
CMD ["bun", "run", "start"]
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { ClientOptions } from "./types.gen";
|
import { type ClientOptions, type Config, createClient, createConfig } from "./client";
|
||||||
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from "./client";
|
import type { ClientOptions as ClientOptions2 } from "./types.gen";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `createClientConfig()` function will be called on client initialization
|
* The `createClientConfig()` function will be called on client initialization
|
||||||
@@ -11,12 +11,12 @@ import { type Config, type ClientOptions as DefaultClientOptions, createClient,
|
|||||||
* `setConfig()`. This is useful for example if you're using Next.js
|
* `setConfig()`. This is useful for example if you're using Next.js
|
||||||
* to ensure your client always has the correct values.
|
* to ensure your client always has the correct values.
|
||||||
*/
|
*/
|
||||||
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (
|
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (
|
||||||
override?: Config<DefaultClientOptions & T>,
|
override?: Config<ClientOptions & T>,
|
||||||
) => Config<Required<DefaultClientOptions> & T>;
|
) => Config<Required<ClientOptions> & T>;
|
||||||
|
|
||||||
export const client = createClient(
|
export const client = createClient(
|
||||||
createConfig<ClientOptions>({
|
createConfig<ClientOptions2>({
|
||||||
baseUrl: "http://192.168.2.42:4096",
|
baseUrl: "http://192.168.2.42:4096",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Client, Config, ResolvedRequestOptions } from "./types.gen";
|
import { createSseClient } from "../core/serverSentEvents.gen";
|
||||||
|
import type { HttpMethod } from "../core/types.gen";
|
||||||
|
import { getValidRequestBody } from "../core/utils.gen";
|
||||||
|
import type { Client, Config, RequestOptions, ResolvedRequestOptions } from "./types.gen";
|
||||||
import {
|
import {
|
||||||
buildUrl,
|
buildUrl,
|
||||||
createConfig,
|
createConfig,
|
||||||
@@ -28,7 +31,7 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
|
|
||||||
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
|
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
|
||||||
|
|
||||||
const request: Client["request"] = async (options) => {
|
const beforeRequest = async (options: RequestOptions) => {
|
||||||
const opts = {
|
const opts = {
|
||||||
..._config,
|
..._config,
|
||||||
...options,
|
...options,
|
||||||
@@ -48,25 +51,32 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
await opts.requestValidator(opts);
|
await opts.requestValidator(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.body && opts.bodySerializer) {
|
if (opts.body !== undefined && opts.bodySerializer) {
|
||||||
opts.serializedBody = opts.bodySerializer(opts.body);
|
opts.serializedBody = opts.bodySerializer(opts.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove Content-Type header if body is empty to avoid sending invalid requests
|
// remove Content-Type header if body is empty to avoid sending invalid requests
|
||||||
if (opts.serializedBody === undefined || opts.serializedBody === "") {
|
if (opts.body === undefined || opts.serializedBody === "") {
|
||||||
opts.headers.delete("Content-Type");
|
opts.headers.delete("Content-Type");
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = buildUrl(opts);
|
const url = buildUrl(opts);
|
||||||
|
|
||||||
|
return { opts, url };
|
||||||
|
};
|
||||||
|
|
||||||
|
const request: Client["request"] = async (options) => {
|
||||||
|
// @ts-expect-error
|
||||||
|
const { opts, url } = await beforeRequest(options);
|
||||||
const requestInit: ReqInit = {
|
const requestInit: ReqInit = {
|
||||||
redirect: "follow",
|
redirect: "follow",
|
||||||
...opts,
|
...opts,
|
||||||
body: opts.serializedBody,
|
body: getValidRequestBody(opts),
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = new Request(url, requestInit);
|
let request = new Request(url, requestInit);
|
||||||
|
|
||||||
for (const fn of interceptors.request._fns) {
|
for (const fn of interceptors.request.fns) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
request = await fn(request, opts);
|
request = await fn(request, opts);
|
||||||
}
|
}
|
||||||
@@ -75,9 +85,37 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
// fetch must be assigned here, otherwise it would throw the error:
|
// fetch must be assigned here, otherwise it would throw the error:
|
||||||
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
||||||
const _fetch = opts.fetch!;
|
const _fetch = opts.fetch!;
|
||||||
let response = await _fetch(request);
|
let response: Response;
|
||||||
|
|
||||||
for (const fn of interceptors.response._fns) {
|
try {
|
||||||
|
response = await _fetch(request);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle fetch exceptions (AbortError, network errors, etc.)
|
||||||
|
let finalError = error;
|
||||||
|
|
||||||
|
for (const fn of interceptors.error.fns) {
|
||||||
|
if (fn) {
|
||||||
|
finalError = (await fn(error, undefined as any, request, opts)) as unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalError = finalError || ({} as unknown);
|
||||||
|
|
||||||
|
if (opts.throwOnError) {
|
||||||
|
throw finalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return error response
|
||||||
|
return opts.responseStyle === "data"
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
error: finalError,
|
||||||
|
request,
|
||||||
|
response: undefined as any,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const fn of interceptors.response.fns) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
response = await fn(response, request, opts);
|
response = await fn(response, request, opts);
|
||||||
}
|
}
|
||||||
@@ -89,18 +127,36 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
const parseAs =
|
||||||
|
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
||||||
|
|
||||||
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||||
|
let emptyData: any;
|
||||||
|
switch (parseAs) {
|
||||||
|
case "arrayBuffer":
|
||||||
|
case "blob":
|
||||||
|
case "text":
|
||||||
|
emptyData = await response[parseAs]();
|
||||||
|
break;
|
||||||
|
case "formData":
|
||||||
|
emptyData = new FormData();
|
||||||
|
break;
|
||||||
|
case "stream":
|
||||||
|
emptyData = response.body;
|
||||||
|
break;
|
||||||
|
case "json":
|
||||||
|
default:
|
||||||
|
emptyData = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
return opts.responseStyle === "data"
|
return opts.responseStyle === "data"
|
||||||
? {}
|
? emptyData
|
||||||
: {
|
: {
|
||||||
data: {},
|
data: emptyData,
|
||||||
...result,
|
...result,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseAs =
|
|
||||||
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
||||||
|
|
||||||
let data: any;
|
let data: any;
|
||||||
switch (parseAs) {
|
switch (parseAs) {
|
||||||
case "arrayBuffer":
|
case "arrayBuffer":
|
||||||
@@ -149,7 +205,7 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
const error = jsonError ?? textError;
|
const error = jsonError ?? textError;
|
||||||
let finalError = error;
|
let finalError = error;
|
||||||
|
|
||||||
for (const fn of interceptors.error._fns) {
|
for (const fn of interceptors.error.fns) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
finalError = (await fn(error, response, request, opts)) as string;
|
finalError = (await fn(error, response, request, opts)) as string;
|
||||||
}
|
}
|
||||||
@@ -170,20 +226,53 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => request({ ...options, method });
|
||||||
|
|
||||||
|
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
|
||||||
|
const { opts, url } = await beforeRequest(options);
|
||||||
|
return createSseClient({
|
||||||
|
...opts,
|
||||||
|
body: opts.body as BodyInit | null | undefined,
|
||||||
|
headers: opts.headers as unknown as Record<string, string>,
|
||||||
|
method,
|
||||||
|
onRequest: async (url, init) => {
|
||||||
|
let request = new Request(url, init);
|
||||||
|
for (const fn of interceptors.request.fns) {
|
||||||
|
if (fn) {
|
||||||
|
request = await fn(request, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildUrl,
|
buildUrl,
|
||||||
connect: (options) => request({ ...options, method: "CONNECT" }),
|
connect: makeMethodFn("CONNECT"),
|
||||||
delete: (options) => request({ ...options, method: "DELETE" }),
|
delete: makeMethodFn("DELETE"),
|
||||||
get: (options) => request({ ...options, method: "GET" }),
|
get: makeMethodFn("GET"),
|
||||||
getConfig,
|
getConfig,
|
||||||
head: (options) => request({ ...options, method: "HEAD" }),
|
head: makeMethodFn("HEAD"),
|
||||||
interceptors,
|
interceptors,
|
||||||
options: (options) => request({ ...options, method: "OPTIONS" }),
|
options: makeMethodFn("OPTIONS"),
|
||||||
patch: (options) => request({ ...options, method: "PATCH" }),
|
patch: makeMethodFn("PATCH"),
|
||||||
post: (options) => request({ ...options, method: "POST" }),
|
post: makeMethodFn("POST"),
|
||||||
put: (options) => request({ ...options, method: "PUT" }),
|
put: makeMethodFn("PUT"),
|
||||||
request,
|
request,
|
||||||
setConfig,
|
setConfig,
|
||||||
trace: (options) => request({ ...options, method: "TRACE" }),
|
sse: {
|
||||||
};
|
connect: makeSseFn("CONNECT"),
|
||||||
|
delete: makeSseFn("DELETE"),
|
||||||
|
get: makeSseFn("GET"),
|
||||||
|
head: makeSseFn("HEAD"),
|
||||||
|
options: makeSseFn("OPTIONS"),
|
||||||
|
patch: makeSseFn("PATCH"),
|
||||||
|
post: makeSseFn("POST"),
|
||||||
|
put: makeSseFn("PUT"),
|
||||||
|
trace: makeSseFn("TRACE"),
|
||||||
|
},
|
||||||
|
trace: makeMethodFn("TRACE"),
|
||||||
|
} as Client;
|
||||||
};
|
};
|
||||||
@@ -8,6 +8,7 @@ export {
|
|||||||
urlSearchParamsBodySerializer,
|
urlSearchParamsBodySerializer,
|
||||||
} from "../core/bodySerializer.gen";
|
} from "../core/bodySerializer.gen";
|
||||||
export { buildClientParams } from "../core/params.gen";
|
export { buildClientParams } from "../core/params.gen";
|
||||||
|
export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen";
|
||||||
export { createClient } from "./client.gen";
|
export { createClient } from "./client.gen";
|
||||||
export type {
|
export type {
|
||||||
Client,
|
Client,
|
||||||
@@ -15,7 +16,6 @@ export type {
|
|||||||
Config,
|
Config,
|
||||||
CreateClientConfig,
|
CreateClientConfig,
|
||||||
Options,
|
Options,
|
||||||
OptionsLegacyParser,
|
|
||||||
RequestOptions,
|
RequestOptions,
|
||||||
RequestResult,
|
RequestResult,
|
||||||
ResolvedRequestOptions,
|
ResolvedRequestOptions,
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Auth } from "../core/auth.gen";
|
import type { Auth } from "../core/auth.gen";
|
||||||
|
import type { ServerSentEventsOptions, ServerSentEventsResult } from "../core/serverSentEvents.gen";
|
||||||
import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen";
|
import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen";
|
||||||
import type { Middleware } from "./utils.gen";
|
import type { Middleware } from "./utils.gen";
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
|||||||
*
|
*
|
||||||
* @default globalThis.fetch
|
* @default globalThis.fetch
|
||||||
*/
|
*/
|
||||||
fetch?: (request: Request) => ReturnType<typeof fetch>;
|
fetch?: typeof fetch;
|
||||||
/**
|
/**
|
||||||
* Please don't use the Fetch client for Next.js applications. The `next`
|
* Please don't use the Fetch client for Next.js applications. The `next`
|
||||||
* options won't have any effect.
|
* options won't have any effect.
|
||||||
@@ -51,13 +52,18 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestOptions<
|
export interface RequestOptions<
|
||||||
|
TData = unknown,
|
||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
Url extends string = string,
|
Url extends string = string,
|
||||||
> extends Config<{
|
> extends Config<{
|
||||||
responseStyle: TResponseStyle;
|
responseStyle: TResponseStyle;
|
||||||
throwOnError: ThrowOnError;
|
throwOnError: ThrowOnError;
|
||||||
}> {
|
}>,
|
||||||
|
Pick<
|
||||||
|
ServerSentEventsOptions<TData>,
|
||||||
|
"onSseError" | "onSseEvent" | "sseDefaultRetryDelay" | "sseMaxRetryAttempts" | "sseMaxRetryDelay"
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* Any body that you want to add to your request.
|
* Any body that you want to add to your request.
|
||||||
*
|
*
|
||||||
@@ -77,7 +83,7 @@ export interface ResolvedRequestOptions<
|
|||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
Url extends string = string,
|
Url extends string = string,
|
||||||
> extends RequestOptions<TResponseStyle, ThrowOnError, Url> {
|
> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
|
||||||
serializedBody?: string;
|
serializedBody?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,17 +134,26 @@ type MethodFn = <
|
|||||||
ThrowOnError extends boolean = false,
|
ThrowOnError extends boolean = false,
|
||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
>(
|
>(
|
||||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method">,
|
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
|
||||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
||||||
|
|
||||||
|
type SseFn = <
|
||||||
|
TData = unknown,
|
||||||
|
TError = unknown,
|
||||||
|
ThrowOnError extends boolean = false,
|
||||||
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
|
>(
|
||||||
|
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
|
||||||
|
) => Promise<ServerSentEventsResult<TData, TError>>;
|
||||||
|
|
||||||
type RequestFn = <
|
type RequestFn = <
|
||||||
TData = unknown,
|
TData = unknown,
|
||||||
TError = unknown,
|
TError = unknown,
|
||||||
ThrowOnError extends boolean = false,
|
ThrowOnError extends boolean = false,
|
||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
>(
|
>(
|
||||||
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method"> &
|
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method"> &
|
||||||
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, "method">,
|
Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, "method">,
|
||||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
|
||||||
|
|
||||||
type BuildUrlFn = <
|
type BuildUrlFn = <
|
||||||
@@ -149,10 +164,10 @@ type BuildUrlFn = <
|
|||||||
url: string;
|
url: string;
|
||||||
},
|
},
|
||||||
>(
|
>(
|
||||||
options: Pick<TData, "url"> & Options<TData>,
|
options: TData & Options<TData>,
|
||||||
) => string;
|
) => string;
|
||||||
|
|
||||||
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
|
||||||
interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
|
interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -181,21 +196,7 @@ type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
|
|||||||
export type Options<
|
export type Options<
|
||||||
TData extends TDataShape = TDataShape,
|
TData extends TDataShape = TDataShape,
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
|
TResponse = unknown,
|
||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
> = OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> & Omit<TData, "url">;
|
> = OmitKeys<RequestOptions<TResponse, TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> &
|
||||||
|
([TData] extends [never] ? unknown : Omit<TData, "url">);
|
||||||
export type OptionsLegacyParser<
|
|
||||||
TData = unknown,
|
|
||||||
ThrowOnError extends boolean = boolean,
|
|
||||||
TResponseStyle extends ResponseStyle = "fields",
|
|
||||||
> = TData extends { body?: any }
|
|
||||||
? TData extends { headers?: any }
|
|
||||||
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "headers" | "url"> & TData
|
|
||||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
|
|
||||||
TData &
|
|
||||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
|
|
||||||
: TData extends { headers?: any }
|
|
||||||
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "headers" | "url"> &
|
|
||||||
TData &
|
|
||||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
|
|
||||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData;
|
|
||||||
@@ -1,88 +1,13 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import { getAuthToken } from "../core/auth.gen";
|
import { getAuthToken } from "../core/auth.gen";
|
||||||
import type { QuerySerializer, QuerySerializerOptions } from "../core/bodySerializer.gen";
|
import type { QuerySerializerOptions } from "../core/bodySerializer.gen";
|
||||||
import { jsonBodySerializer } from "../core/bodySerializer.gen";
|
import { jsonBodySerializer } from "../core/bodySerializer.gen";
|
||||||
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen";
|
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen";
|
||||||
|
import { getUrl } from "../core/utils.gen";
|
||||||
import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen";
|
import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen";
|
||||||
|
|
||||||
interface PathSerializer {
|
export const createQuerySerializer = <T = unknown>({ parameters = {}, ...args }: QuerySerializerOptions = {}) => {
|
||||||
path: Record<string, unknown>;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
||||||
|
|
||||||
type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited";
|
|
||||||
type MatrixStyle = "label" | "matrix" | "simple";
|
|
||||||
type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
|
|
||||||
|
|
||||||
const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
|
||||||
let url = _url;
|
|
||||||
const matches = _url.match(PATH_PARAM_RE);
|
|
||||||
if (matches) {
|
|
||||||
for (const match of matches) {
|
|
||||||
let explode = false;
|
|
||||||
let name = match.substring(1, match.length - 1);
|
|
||||||
let style: ArraySeparatorStyle = "simple";
|
|
||||||
|
|
||||||
if (name.endsWith("*")) {
|
|
||||||
explode = true;
|
|
||||||
name = name.substring(0, name.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.startsWith(".")) {
|
|
||||||
name = name.substring(1);
|
|
||||||
style = "label";
|
|
||||||
} else if (name.startsWith(";")) {
|
|
||||||
name = name.substring(1);
|
|
||||||
style = "matrix";
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = path[name];
|
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "object") {
|
|
||||||
url = url.replace(
|
|
||||||
match,
|
|
||||||
serializeObjectParam({
|
|
||||||
explode,
|
|
||||||
name,
|
|
||||||
style,
|
|
||||||
value: value as Record<string, unknown>,
|
|
||||||
valueOnly: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style === "matrix") {
|
|
||||||
url = url.replace(
|
|
||||||
match,
|
|
||||||
`;${serializePrimitiveParam({
|
|
||||||
name,
|
|
||||||
value: value as string,
|
|
||||||
})}`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string));
|
|
||||||
url = url.replace(match, replaceValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createQuerySerializer = <T = unknown>({ allowReserved, array, object }: QuerySerializerOptions = {}) => {
|
|
||||||
const querySerializer = (queryParams: T) => {
|
const querySerializer = (queryParams: T) => {
|
||||||
const search: string[] = [];
|
const search: string[] = [];
|
||||||
if (queryParams && typeof queryParams === "object") {
|
if (queryParams && typeof queryParams === "object") {
|
||||||
@@ -93,29 +18,31 @@ export const createQuerySerializer = <T = unknown>({ allowReserved, array, objec
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const options = parameters[name] || args;
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const serializedArray = serializeArrayParam({
|
const serializedArray = serializeArrayParam({
|
||||||
allowReserved,
|
allowReserved: options.allowReserved,
|
||||||
explode: true,
|
explode: true,
|
||||||
name,
|
name,
|
||||||
style: "form",
|
style: "form",
|
||||||
value,
|
value,
|
||||||
...array,
|
...options.array,
|
||||||
});
|
});
|
||||||
if (serializedArray) search.push(serializedArray);
|
if (serializedArray) search.push(serializedArray);
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
const serializedObject = serializeObjectParam({
|
const serializedObject = serializeObjectParam({
|
||||||
allowReserved,
|
allowReserved: options.allowReserved,
|
||||||
explode: true,
|
explode: true,
|
||||||
name,
|
name,
|
||||||
style: "deepObject",
|
style: "deepObject",
|
||||||
value: value as Record<string, unknown>,
|
value: value as Record<string, unknown>,
|
||||||
...object,
|
...options.object,
|
||||||
});
|
});
|
||||||
if (serializedObject) search.push(serializedObject);
|
if (serializedObject) search.push(serializedObject);
|
||||||
} else {
|
} else {
|
||||||
const serializedPrimitive = serializePrimitiveParam({
|
const serializedPrimitive = serializePrimitiveParam({
|
||||||
allowReserved,
|
allowReserved: options.allowReserved,
|
||||||
name,
|
name,
|
||||||
value: value as string,
|
value: value as string,
|
||||||
});
|
});
|
||||||
@@ -216,8 +143,8 @@ export const setAuthParams = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildUrl: Client["buildUrl"] = (options) => {
|
export const buildUrl: Client["buildUrl"] = (options) =>
|
||||||
const url = getUrl({
|
getUrl({
|
||||||
baseUrl: options.baseUrl as string,
|
baseUrl: options.baseUrl as string,
|
||||||
path: options.path,
|
path: options.path,
|
||||||
query: options.query,
|
query: options.query,
|
||||||
@@ -227,36 +154,6 @@ export const buildUrl: Client["buildUrl"] = (options) => {
|
|||||||
: createQuerySerializer(options.querySerializer),
|
: createQuerySerializer(options.querySerializer),
|
||||||
url: options.url,
|
url: options.url,
|
||||||
});
|
});
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getUrl = ({
|
|
||||||
baseUrl,
|
|
||||||
path,
|
|
||||||
query,
|
|
||||||
querySerializer,
|
|
||||||
url: _url,
|
|
||||||
}: {
|
|
||||||
baseUrl?: string;
|
|
||||||
path?: Record<string, unknown>;
|
|
||||||
query?: Record<string, unknown>;
|
|
||||||
querySerializer: QuerySerializer;
|
|
||||||
url: string;
|
|
||||||
}) => {
|
|
||||||
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
|
||||||
let url = (baseUrl ?? "") + pathUrl;
|
|
||||||
if (path) {
|
|
||||||
url = defaultPathSerializer({ path, url });
|
|
||||||
}
|
|
||||||
let search = query ? querySerializer(query) : "";
|
|
||||||
if (search.startsWith("?")) {
|
|
||||||
search = search.substring(1);
|
|
||||||
}
|
|
||||||
if (search) {
|
|
||||||
url += `?${search}`;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mergeConfigs = (a: Config, b: Config): Config => {
|
export const mergeConfigs = (a: Config, b: Config): Config => {
|
||||||
const config = { ...a, ...b };
|
const config = { ...a, ...b };
|
||||||
@@ -267,14 +164,22 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
|
|||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const headersEntries = (headers: Headers): Array<[string, string]> => {
|
||||||
|
const entries: Array<[string, string]> = [];
|
||||||
|
headers.forEach((value, key) => {
|
||||||
|
entries.push([key, value]);
|
||||||
|
});
|
||||||
|
return entries;
|
||||||
|
};
|
||||||
|
|
||||||
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||||
const mergedHeaders = new Headers();
|
const mergedHeaders = new Headers();
|
||||||
for (const header of headers) {
|
for (const header of headers) {
|
||||||
if (!header || typeof header !== "object") {
|
if (!header) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iterator = header instanceof Headers ? header.entries() : Object.entries(header);
|
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
|
||||||
|
|
||||||
for (const [key, value] of iterator) {
|
for (const [key, value] of iterator) {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
@@ -305,61 +210,53 @@ type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Pr
|
|||||||
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>;
|
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>;
|
||||||
|
|
||||||
class Interceptors<Interceptor> {
|
class Interceptors<Interceptor> {
|
||||||
_fns: (Interceptor | null)[];
|
fns: Array<Interceptor | null> = [];
|
||||||
|
|
||||||
constructor() {
|
clear(): void {
|
||||||
this._fns = [];
|
this.fns = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
eject(id: number | Interceptor): void {
|
||||||
this._fns = [];
|
const index = this.getInterceptorIndex(id);
|
||||||
|
if (this.fns[index]) {
|
||||||
|
this.fns[index] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exists(id: number | Interceptor): boolean {
|
||||||
|
const index = this.getInterceptorIndex(id);
|
||||||
|
return Boolean(this.fns[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInterceptorIndex(id: number | Interceptor): number {
|
getInterceptorIndex(id: number | Interceptor): number {
|
||||||
if (typeof id === "number") {
|
if (typeof id === "number") {
|
||||||
return this._fns[id] ? id : -1;
|
return this.fns[id] ? id : -1;
|
||||||
} else {
|
|
||||||
return this._fns.indexOf(id);
|
|
||||||
}
|
}
|
||||||
}
|
return this.fns.indexOf(id);
|
||||||
exists(id: number | Interceptor) {
|
|
||||||
const index = this.getInterceptorIndex(id);
|
|
||||||
return !!this._fns[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eject(id: number | Interceptor) {
|
update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false {
|
||||||
const index = this.getInterceptorIndex(id);
|
const index = this.getInterceptorIndex(id);
|
||||||
if (this._fns[index]) {
|
if (this.fns[index]) {
|
||||||
this._fns[index] = null;
|
this.fns[index] = fn;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update(id: number | Interceptor, fn: Interceptor) {
|
|
||||||
const index = this.getInterceptorIndex(id);
|
|
||||||
if (this._fns[index]) {
|
|
||||||
this._fns[index] = fn;
|
|
||||||
return id;
|
return id;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
use(fn: Interceptor) {
|
use(fn: Interceptor): number {
|
||||||
this._fns = [...this._fns, fn];
|
this.fns.push(fn);
|
||||||
return this._fns.length - 1;
|
return this.fns.length - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `createInterceptors()` response, meant for external use as it does not
|
|
||||||
// expose internals
|
|
||||||
export interface Middleware<Req, Res, Err, Options> {
|
export interface Middleware<Req, Res, Err, Options> {
|
||||||
error: Pick<Interceptors<ErrInterceptor<Err, Res, Req, Options>>, "eject" | "use">;
|
error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>;
|
||||||
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">;
|
request: Interceptors<ReqInterceptor<Req, Options>>;
|
||||||
response: Pick<Interceptors<ResInterceptor<Res, Req, Options>>, "eject" | "use">;
|
response: Interceptors<ResInterceptor<Res, Req, Options>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not add `Middleware` as return type so we can use _fns internally
|
export const createInterceptors = <Req, Res, Err, Options>(): Middleware<Req, Res, Err, Options> => ({
|
||||||
export const createInterceptors = <Req, Res, Err, Options>() => ({
|
|
||||||
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
|
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
|
||||||
request: new Interceptors<ReqInterceptor<Req, Options>>(),
|
request: new Interceptors<ReqInterceptor<Req, Options>>(),
|
||||||
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
|
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
|
||||||
@@ -6,11 +6,19 @@ export type QuerySerializer = (query: Record<string, unknown>) => string;
|
|||||||
|
|
||||||
export type BodySerializer = (body: any) => any;
|
export type BodySerializer = (body: any) => any;
|
||||||
|
|
||||||
export interface QuerySerializerOptions {
|
type QuerySerializerOptionsObject = {
|
||||||
allowReserved?: boolean;
|
allowReserved?: boolean;
|
||||||
array?: SerializerOptions<ArrayStyle>;
|
array?: Partial<SerializerOptions<ArrayStyle>>;
|
||||||
object?: SerializerOptions<ObjectStyle>;
|
object?: Partial<SerializerOptions<ObjectStyle>>;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type QuerySerializerOptions = QuerySerializerOptionsObject & {
|
||||||
|
/**
|
||||||
|
* Per-parameter serialization overrides. When provided, these settings
|
||||||
|
* override the global array/object settings for specific parameter names.
|
||||||
|
*/
|
||||||
|
parameters?: Record<string, QuerySerializerOptionsObject>;
|
||||||
|
};
|
||||||
|
|
||||||
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
|
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
|
||||||
if (typeof value === "string" || value instanceof Blob) {
|
if (typeof value === "string" || value instanceof Blob) {
|
||||||
@@ -22,6 +22,17 @@ export type Field =
|
|||||||
*/
|
*/
|
||||||
key?: string;
|
key?: string;
|
||||||
map?: string;
|
map?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* Field name. This is the name we want the user to see and use.
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
/**
|
||||||
|
* Field mapped name. This is the name we want to use in the request.
|
||||||
|
* If `in` is omitted, `map` aliases `key` to the transport layer.
|
||||||
|
*/
|
||||||
|
map: Slot;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Fields {
|
export interface Fields {
|
||||||
@@ -41,10 +52,14 @@ const extraPrefixes = Object.entries(extraPrefixesMap);
|
|||||||
|
|
||||||
type KeyMap = Map<
|
type KeyMap = Map<
|
||||||
string,
|
string,
|
||||||
{
|
| {
|
||||||
in: Slot;
|
in: Slot;
|
||||||
map?: string;
|
map?: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
in?: never;
|
||||||
|
map: Slot;
|
||||||
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
|
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
|
||||||
@@ -60,6 +75,10 @@ const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
|
|||||||
map: config.map,
|
map: config.map,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if ("key" in config) {
|
||||||
|
map.set(config.key, {
|
||||||
|
map: config.map,
|
||||||
|
});
|
||||||
} else if (config.args) {
|
} else if (config.args) {
|
||||||
buildKeyMap(config.args, map);
|
buildKeyMap(config.args, map);
|
||||||
}
|
}
|
||||||
@@ -108,7 +127,9 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
|
|||||||
if (config.key) {
|
if (config.key) {
|
||||||
const field = map.get(config.key)!;
|
const field = map.get(config.key)!;
|
||||||
const name = field.map || config.key;
|
const name = field.map || config.key;
|
||||||
(params[field.in] as Record<string, unknown>)[name] = arg;
|
if (field.in) {
|
||||||
|
(params[field.in] as Record<string, unknown>)[name] = arg;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
params.body = arg;
|
params.body = arg;
|
||||||
}
|
}
|
||||||
@@ -117,16 +138,20 @@ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsCo
|
|||||||
const field = map.get(key);
|
const field = map.get(key);
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
const name = field.map || key;
|
if (field.in) {
|
||||||
(params[field.in] as Record<string, unknown>)[name] = value;
|
const name = field.map || key;
|
||||||
|
(params[field.in] as Record<string, unknown>)[name] = value;
|
||||||
|
} else {
|
||||||
|
params[field.map] = value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix));
|
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix));
|
||||||
|
|
||||||
if (extra) {
|
if (extra) {
|
||||||
const [prefix, slot] = extra;
|
const [prefix, slot] = extra;
|
||||||
(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value;
|
(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value;
|
||||||
} else {
|
} else if ("allowExtra" in config && config.allowExtra) {
|
||||||
for (const [slot, allowed] of Object.entries(config.allowExtra ?? {})) {
|
for (const [slot, allowed] of Object.entries(config.allowExtra)) {
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
||||||
break;
|
break;
|
||||||
111
app/client/api-client/core/queryKeySerializer.gen.ts
Normal file
111
app/client/api-client/core/queryKeySerializer.gen.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON-friendly union that mirrors what Pinia Colada can hash.
|
||||||
|
*/
|
||||||
|
export type JsonValue = null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
|
||||||
|
*/
|
||||||
|
export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
|
||||||
|
if (value === undefined || typeof value === "function" || typeof value === "symbol") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (typeof value === "bigint") {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value.toISOString();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely stringifies a value and parses it back into a JsonValue.
|
||||||
|
*/
|
||||||
|
export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
|
||||||
|
try {
|
||||||
|
const json = JSON.stringify(input, queryKeyJsonReplacer);
|
||||||
|
if (json === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return JSON.parse(json) as JsonValue;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects plain objects (including objects with a null prototype).
|
||||||
|
*/
|
||||||
|
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
||||||
|
if (value === null || typeof value !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const prototype = Object.getPrototypeOf(value as object);
|
||||||
|
return prototype === Object.prototype || prototype === null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns URLSearchParams into a sorted JSON object for deterministic keys.
|
||||||
|
*/
|
||||||
|
const serializeSearchParams = (params: URLSearchParams): JsonValue => {
|
||||||
|
const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b));
|
||||||
|
const result: Record<string, JsonValue> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of entries) {
|
||||||
|
const existing = result[key];
|
||||||
|
if (existing === undefined) {
|
||||||
|
result[key] = value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(existing)) {
|
||||||
|
(existing as string[]).push(value);
|
||||||
|
} else {
|
||||||
|
result[key] = [existing, value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes any accepted value into a JSON-friendly shape for query keys.
|
||||||
|
*/
|
||||||
|
export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => {
|
||||||
|
if (value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined || typeof value === "function" || typeof value === "symbol") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "bigint") {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return stringifyToJsonValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) {
|
||||||
|
return serializeSearchParams(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlainObject(value)) {
|
||||||
|
return stringifyToJsonValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
237
app/client/api-client/core/serverSentEvents.gen.ts
Normal file
237
app/client/api-client/core/serverSentEvents.gen.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
import type { Config } from "./types.gen";
|
||||||
|
|
||||||
|
export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method"> &
|
||||||
|
Pick<Config, "method" | "responseTransformer" | "responseValidator"> & {
|
||||||
|
/**
|
||||||
|
* Fetch API implementation. You can use this option to provide a custom
|
||||||
|
* fetch instance.
|
||||||
|
*
|
||||||
|
* @default globalThis.fetch
|
||||||
|
*/
|
||||||
|
fetch?: typeof fetch;
|
||||||
|
/**
|
||||||
|
* Implementing clients can call request interceptors inside this hook.
|
||||||
|
*/
|
||||||
|
onRequest?: (url: string, init: RequestInit) => Promise<Request>;
|
||||||
|
/**
|
||||||
|
* Callback invoked when a network or parsing error occurs during streaming.
|
||||||
|
*
|
||||||
|
* This option applies only if the endpoint returns a stream of events.
|
||||||
|
*
|
||||||
|
* @param error The error that occurred.
|
||||||
|
*/
|
||||||
|
onSseError?: (error: unknown) => void;
|
||||||
|
/**
|
||||||
|
* Callback invoked when an event is streamed from the server.
|
||||||
|
*
|
||||||
|
* This option applies only if the endpoint returns a stream of events.
|
||||||
|
*
|
||||||
|
* @param event Event streamed from the server.
|
||||||
|
* @returns Nothing (void).
|
||||||
|
*/
|
||||||
|
onSseEvent?: (event: StreamEvent<TData>) => void;
|
||||||
|
serializedBody?: RequestInit["body"];
|
||||||
|
/**
|
||||||
|
* Default retry delay in milliseconds.
|
||||||
|
*
|
||||||
|
* This option applies only if the endpoint returns a stream of events.
|
||||||
|
*
|
||||||
|
* @default 3000
|
||||||
|
*/
|
||||||
|
sseDefaultRetryDelay?: number;
|
||||||
|
/**
|
||||||
|
* Maximum number of retry attempts before giving up.
|
||||||
|
*/
|
||||||
|
sseMaxRetryAttempts?: number;
|
||||||
|
/**
|
||||||
|
* Maximum retry delay in milliseconds.
|
||||||
|
*
|
||||||
|
* Applies only when exponential backoff is used.
|
||||||
|
*
|
||||||
|
* This option applies only if the endpoint returns a stream of events.
|
||||||
|
*
|
||||||
|
* @default 30000
|
||||||
|
*/
|
||||||
|
sseMaxRetryDelay?: number;
|
||||||
|
/**
|
||||||
|
* Optional sleep function for retry backoff.
|
||||||
|
*
|
||||||
|
* Defaults to using `setTimeout`.
|
||||||
|
*/
|
||||||
|
sseSleepFn?: (ms: number) => Promise<void>;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface StreamEvent<TData = unknown> {
|
||||||
|
data: TData;
|
||||||
|
event?: string;
|
||||||
|
id?: string;
|
||||||
|
retry?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = {
|
||||||
|
stream: AsyncGenerator<TData extends Record<string, unknown> ? TData[keyof TData] : TData, TReturn, TNext>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSseClient = <TData = unknown>({
|
||||||
|
onRequest,
|
||||||
|
onSseError,
|
||||||
|
onSseEvent,
|
||||||
|
responseTransformer,
|
||||||
|
responseValidator,
|
||||||
|
sseDefaultRetryDelay,
|
||||||
|
sseMaxRetryAttempts,
|
||||||
|
sseMaxRetryDelay,
|
||||||
|
sseSleepFn,
|
||||||
|
url,
|
||||||
|
...options
|
||||||
|
}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
|
||||||
|
let lastEventId: string | undefined;
|
||||||
|
|
||||||
|
const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
|
||||||
|
|
||||||
|
const createStream = async function* () {
|
||||||
|
let retryDelay: number = sseDefaultRetryDelay ?? 3000;
|
||||||
|
let attempt = 0;
|
||||||
|
const signal = options.signal ?? new AbortController().signal;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (signal.aborted) break;
|
||||||
|
|
||||||
|
attempt++;
|
||||||
|
|
||||||
|
const headers =
|
||||||
|
options.headers instanceof Headers
|
||||||
|
? options.headers
|
||||||
|
: new Headers(options.headers as Record<string, string> | undefined);
|
||||||
|
|
||||||
|
if (lastEventId !== undefined) {
|
||||||
|
headers.set("Last-Event-ID", lastEventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestInit: RequestInit = {
|
||||||
|
redirect: "follow",
|
||||||
|
...options,
|
||||||
|
body: options.serializedBody,
|
||||||
|
headers,
|
||||||
|
signal,
|
||||||
|
};
|
||||||
|
let request = new Request(url, requestInit);
|
||||||
|
if (onRequest) {
|
||||||
|
request = await onRequest(url, requestInit);
|
||||||
|
}
|
||||||
|
// fetch must be assigned here, otherwise it would throw the error:
|
||||||
|
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
||||||
|
const _fetch = options.fetch ?? globalThis.fetch;
|
||||||
|
const response = await _fetch(request);
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
|
||||||
|
|
||||||
|
if (!response.body) throw new Error("No body in SSE response");
|
||||||
|
|
||||||
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
||||||
|
|
||||||
|
let buffer = "";
|
||||||
|
|
||||||
|
const abortHandler = () => {
|
||||||
|
try {
|
||||||
|
reader.cancel();
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
signal.addEventListener("abort", abortHandler);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
buffer += value;
|
||||||
|
|
||||||
|
const chunks = buffer.split("\n\n");
|
||||||
|
buffer = chunks.pop() ?? "";
|
||||||
|
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
const lines = chunk.split("\n");
|
||||||
|
const dataLines: Array<string> = [];
|
||||||
|
let eventName: string | undefined;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith("data:")) {
|
||||||
|
dataLines.push(line.replace(/^data:\s*/, ""));
|
||||||
|
} else if (line.startsWith("event:")) {
|
||||||
|
eventName = line.replace(/^event:\s*/, "");
|
||||||
|
} else if (line.startsWith("id:")) {
|
||||||
|
lastEventId = line.replace(/^id:\s*/, "");
|
||||||
|
} else if (line.startsWith("retry:")) {
|
||||||
|
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
|
||||||
|
if (!Number.isNaN(parsed)) {
|
||||||
|
retryDelay = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: unknown;
|
||||||
|
let parsedJson = false;
|
||||||
|
|
||||||
|
if (dataLines.length) {
|
||||||
|
const rawData = dataLines.join("\n");
|
||||||
|
try {
|
||||||
|
data = JSON.parse(rawData);
|
||||||
|
parsedJson = true;
|
||||||
|
} catch {
|
||||||
|
data = rawData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedJson) {
|
||||||
|
if (responseValidator) {
|
||||||
|
await responseValidator(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseTransformer) {
|
||||||
|
data = await responseTransformer(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSseEvent?.({
|
||||||
|
data,
|
||||||
|
event: eventName,
|
||||||
|
id: lastEventId,
|
||||||
|
retry: retryDelay,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dataLines.length) {
|
||||||
|
yield data as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
signal.removeEventListener("abort", abortHandler);
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
break; // exit loop on normal completion
|
||||||
|
} catch (error) {
|
||||||
|
// connection failed or aborted; retry after delay
|
||||||
|
onSseError?.(error);
|
||||||
|
|
||||||
|
if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) {
|
||||||
|
break; // stop after firing error
|
||||||
|
}
|
||||||
|
|
||||||
|
// exponential backoff: double retry each attempt, cap at 30s
|
||||||
|
const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000);
|
||||||
|
await sleep(backoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stream = createStream();
|
||||||
|
|
||||||
|
return { stream };
|
||||||
|
};
|
||||||
@@ -3,24 +3,19 @@
|
|||||||
import type { Auth, AuthToken } from "./auth.gen";
|
import type { Auth, AuthToken } from "./auth.gen";
|
||||||
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen";
|
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen";
|
||||||
|
|
||||||
export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
|
export type HttpMethod = "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
||||||
|
|
||||||
|
export type Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never, SseFn = never> = {
|
||||||
/**
|
/**
|
||||||
* Returns the final request URL.
|
* Returns the final request URL.
|
||||||
*/
|
*/
|
||||||
buildUrl: BuildUrlFn;
|
buildUrl: BuildUrlFn;
|
||||||
connect: MethodFn;
|
|
||||||
delete: MethodFn;
|
|
||||||
get: MethodFn;
|
|
||||||
getConfig: () => Config;
|
getConfig: () => Config;
|
||||||
head: MethodFn;
|
|
||||||
options: MethodFn;
|
|
||||||
patch: MethodFn;
|
|
||||||
post: MethodFn;
|
|
||||||
put: MethodFn;
|
|
||||||
request: RequestFn;
|
request: RequestFn;
|
||||||
setConfig: (config: Config) => Config;
|
setConfig: (config: Config) => Config;
|
||||||
trace: MethodFn;
|
} & {
|
||||||
}
|
[K in HttpMethod]: MethodFn;
|
||||||
|
} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } });
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +42,7 @@ export interface Config {
|
|||||||
*
|
*
|
||||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
||||||
*/
|
*/
|
||||||
method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE";
|
method?: Uppercase<HttpMethod>;
|
||||||
/**
|
/**
|
||||||
* A function for serializing request query parameters. By default, arrays
|
* A function for serializing request query parameters. By default, arrays
|
||||||
* will be exploded in form style, objects will be exploded in deepObject
|
* will be exploded in form style, objects will be exploded in deepObject
|
||||||
137
app/client/api-client/core/utils.gen.ts
Normal file
137
app/client/api-client/core/utils.gen.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
import type { BodySerializer, QuerySerializer } from "./bodySerializer.gen";
|
||||||
|
import {
|
||||||
|
type ArraySeparatorStyle,
|
||||||
|
serializeArrayParam,
|
||||||
|
serializeObjectParam,
|
||||||
|
serializePrimitiveParam,
|
||||||
|
} from "./pathSerializer.gen";
|
||||||
|
|
||||||
|
export interface PathSerializer {
|
||||||
|
path: Record<string, unknown>;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
||||||
|
|
||||||
|
export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
||||||
|
let url = _url;
|
||||||
|
const matches = _url.match(PATH_PARAM_RE);
|
||||||
|
if (matches) {
|
||||||
|
for (const match of matches) {
|
||||||
|
let explode = false;
|
||||||
|
let name = match.substring(1, match.length - 1);
|
||||||
|
let style: ArraySeparatorStyle = "simple";
|
||||||
|
|
||||||
|
if (name.endsWith("*")) {
|
||||||
|
explode = true;
|
||||||
|
name = name.substring(0, name.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith(".")) {
|
||||||
|
name = name.substring(1);
|
||||||
|
style = "label";
|
||||||
|
} else if (name.startsWith(";")) {
|
||||||
|
name = name.substring(1);
|
||||||
|
style = "matrix";
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = path[name];
|
||||||
|
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "object") {
|
||||||
|
url = url.replace(
|
||||||
|
match,
|
||||||
|
serializeObjectParam({
|
||||||
|
explode,
|
||||||
|
name,
|
||||||
|
style,
|
||||||
|
value: value as Record<string, unknown>,
|
||||||
|
valueOnly: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style === "matrix") {
|
||||||
|
url = url.replace(
|
||||||
|
match,
|
||||||
|
`;${serializePrimitiveParam({
|
||||||
|
name,
|
||||||
|
value: value as string,
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string));
|
||||||
|
url = url.replace(match, replaceValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUrl = ({
|
||||||
|
baseUrl,
|
||||||
|
path,
|
||||||
|
query,
|
||||||
|
querySerializer,
|
||||||
|
url: _url,
|
||||||
|
}: {
|
||||||
|
baseUrl?: string;
|
||||||
|
path?: Record<string, unknown>;
|
||||||
|
query?: Record<string, unknown>;
|
||||||
|
querySerializer: QuerySerializer;
|
||||||
|
url: string;
|
||||||
|
}) => {
|
||||||
|
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
||||||
|
let url = (baseUrl ?? "") + pathUrl;
|
||||||
|
if (path) {
|
||||||
|
url = defaultPathSerializer({ path, url });
|
||||||
|
}
|
||||||
|
let search = query ? querySerializer(query) : "";
|
||||||
|
if (search.startsWith("?")) {
|
||||||
|
search = search.substring(1);
|
||||||
|
}
|
||||||
|
if (search) {
|
||||||
|
url += `?${search}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getValidRequestBody(options: {
|
||||||
|
body?: unknown;
|
||||||
|
bodySerializer?: BodySerializer | null;
|
||||||
|
serializedBody?: unknown;
|
||||||
|
}) {
|
||||||
|
const hasBody = options.body !== undefined;
|
||||||
|
const isSerializedBody = hasBody && options.bodySerializer;
|
||||||
|
|
||||||
|
if (isSerializedBody) {
|
||||||
|
if ("serializedBody" in options) {
|
||||||
|
const hasSerializedBody = options.serializedBody !== undefined && options.serializedBody !== "";
|
||||||
|
|
||||||
|
return hasSerializedBody ? options.serializedBody : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not all clients implement a serializedBody property (i.e. client-axios)
|
||||||
|
return options.body !== "" ? options.body : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// plain/text body
|
||||||
|
if (hasBody) {
|
||||||
|
return options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no body was provided
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
export * from "./types.gen";
|
|
||||||
|
export type * from "./types.gen";
|
||||||
export * from "./sdk.gen";
|
export * from "./sdk.gen";
|
||||||
@@ -1,92 +1,92 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Options as ClientOptions, TDataShape, Client } from "./client";
|
import type { Client, Options as Options2, TDataShape } from "./client";
|
||||||
|
import { client } from "./client.gen";
|
||||||
import type {
|
import type {
|
||||||
RegisterData,
|
BrowseFilesystemData,
|
||||||
RegisterResponses,
|
BrowseFilesystemResponses,
|
||||||
|
ChangePasswordData,
|
||||||
|
ChangePasswordResponses,
|
||||||
|
CreateBackupScheduleData,
|
||||||
|
CreateBackupScheduleResponses,
|
||||||
|
CreateRepositoryData,
|
||||||
|
CreateRepositoryResponses,
|
||||||
|
CreateVolumeData,
|
||||||
|
CreateVolumeResponses,
|
||||||
|
DeleteBackupScheduleData,
|
||||||
|
DeleteBackupScheduleResponses,
|
||||||
|
DeleteRepositoryData,
|
||||||
|
DeleteRepositoryResponses,
|
||||||
|
DeleteVolumeData,
|
||||||
|
DeleteVolumeResponses,
|
||||||
|
DoctorRepositoryData,
|
||||||
|
DoctorRepositoryResponses,
|
||||||
|
DownloadResticPasswordData,
|
||||||
|
DownloadResticPasswordResponses,
|
||||||
|
GetBackupScheduleData,
|
||||||
|
GetBackupScheduleForVolumeData,
|
||||||
|
GetBackupScheduleForVolumeResponses,
|
||||||
|
GetBackupScheduleResponses,
|
||||||
|
GetContainersUsingVolumeData,
|
||||||
|
GetContainersUsingVolumeErrors,
|
||||||
|
GetContainersUsingVolumeResponses,
|
||||||
|
GetMeData,
|
||||||
|
GetMeResponses,
|
||||||
|
GetRepositoryData,
|
||||||
|
GetRepositoryResponses,
|
||||||
|
GetSnapshotDetailsData,
|
||||||
|
GetSnapshotDetailsResponses,
|
||||||
|
GetStatusData,
|
||||||
|
GetStatusResponses,
|
||||||
|
GetSystemInfoData,
|
||||||
|
GetSystemInfoResponses,
|
||||||
|
GetVolumeData,
|
||||||
|
GetVolumeErrors,
|
||||||
|
GetVolumeResponses,
|
||||||
|
HealthCheckVolumeData,
|
||||||
|
HealthCheckVolumeErrors,
|
||||||
|
HealthCheckVolumeResponses,
|
||||||
|
ListBackupSchedulesData,
|
||||||
|
ListBackupSchedulesResponses,
|
||||||
|
ListFilesData,
|
||||||
|
ListFilesResponses,
|
||||||
|
ListRcloneRemotesData,
|
||||||
|
ListRcloneRemotesResponses,
|
||||||
|
ListRepositoriesData,
|
||||||
|
ListRepositoriesResponses,
|
||||||
|
ListSnapshotFilesData,
|
||||||
|
ListSnapshotFilesResponses,
|
||||||
|
ListSnapshotsData,
|
||||||
|
ListSnapshotsResponses,
|
||||||
|
ListVolumesData,
|
||||||
|
ListVolumesResponses,
|
||||||
LoginData,
|
LoginData,
|
||||||
LoginResponses,
|
LoginResponses,
|
||||||
LogoutData,
|
LogoutData,
|
||||||
LogoutResponses,
|
LogoutResponses,
|
||||||
GetMeData,
|
|
||||||
GetMeResponses,
|
|
||||||
GetStatusData,
|
|
||||||
GetStatusResponses,
|
|
||||||
ChangePasswordData,
|
|
||||||
ChangePasswordResponses,
|
|
||||||
ListVolumesData,
|
|
||||||
ListVolumesResponses,
|
|
||||||
CreateVolumeData,
|
|
||||||
CreateVolumeResponses,
|
|
||||||
TestConnectionData,
|
|
||||||
TestConnectionResponses,
|
|
||||||
DeleteVolumeData,
|
|
||||||
DeleteVolumeResponses,
|
|
||||||
GetVolumeData,
|
|
||||||
GetVolumeResponses,
|
|
||||||
GetVolumeErrors,
|
|
||||||
UpdateVolumeData,
|
|
||||||
UpdateVolumeResponses,
|
|
||||||
UpdateVolumeErrors,
|
|
||||||
GetContainersUsingVolumeData,
|
|
||||||
GetContainersUsingVolumeResponses,
|
|
||||||
GetContainersUsingVolumeErrors,
|
|
||||||
MountVolumeData,
|
MountVolumeData,
|
||||||
MountVolumeResponses,
|
MountVolumeResponses,
|
||||||
UnmountVolumeData,
|
RegisterData,
|
||||||
UnmountVolumeResponses,
|
RegisterResponses,
|
||||||
HealthCheckVolumeData,
|
|
||||||
HealthCheckVolumeResponses,
|
|
||||||
HealthCheckVolumeErrors,
|
|
||||||
ListFilesData,
|
|
||||||
ListFilesResponses,
|
|
||||||
BrowseFilesystemData,
|
|
||||||
BrowseFilesystemResponses,
|
|
||||||
ListRepositoriesData,
|
|
||||||
ListRepositoriesResponses,
|
|
||||||
CreateRepositoryData,
|
|
||||||
CreateRepositoryResponses,
|
|
||||||
ListRcloneRemotesData,
|
|
||||||
ListRcloneRemotesResponses,
|
|
||||||
DeleteRepositoryData,
|
|
||||||
DeleteRepositoryResponses,
|
|
||||||
GetRepositoryData,
|
|
||||||
GetRepositoryResponses,
|
|
||||||
ListSnapshotsData,
|
|
||||||
ListSnapshotsResponses,
|
|
||||||
GetSnapshotDetailsData,
|
|
||||||
GetSnapshotDetailsResponses,
|
|
||||||
ListSnapshotFilesData,
|
|
||||||
ListSnapshotFilesResponses,
|
|
||||||
RestoreSnapshotData,
|
RestoreSnapshotData,
|
||||||
RestoreSnapshotResponses,
|
RestoreSnapshotResponses,
|
||||||
DoctorRepositoryData,
|
|
||||||
DoctorRepositoryResponses,
|
|
||||||
ListBackupSchedulesData,
|
|
||||||
ListBackupSchedulesResponses,
|
|
||||||
CreateBackupScheduleData,
|
|
||||||
CreateBackupScheduleResponses,
|
|
||||||
DeleteBackupScheduleData,
|
|
||||||
DeleteBackupScheduleResponses,
|
|
||||||
GetBackupScheduleData,
|
|
||||||
GetBackupScheduleResponses,
|
|
||||||
UpdateBackupScheduleData,
|
|
||||||
UpdateBackupScheduleResponses,
|
|
||||||
GetBackupScheduleForVolumeData,
|
|
||||||
GetBackupScheduleForVolumeResponses,
|
|
||||||
RunBackupNowData,
|
RunBackupNowData,
|
||||||
RunBackupNowResponses,
|
RunBackupNowResponses,
|
||||||
StopBackupData,
|
StopBackupData,
|
||||||
StopBackupResponses,
|
|
||||||
StopBackupErrors,
|
StopBackupErrors,
|
||||||
GetSystemInfoData,
|
StopBackupResponses,
|
||||||
GetSystemInfoResponses,
|
TestConnectionData,
|
||||||
DownloadResticPasswordData,
|
TestConnectionResponses,
|
||||||
DownloadResticPasswordResponses,
|
UnmountVolumeData,
|
||||||
|
UnmountVolumeResponses,
|
||||||
|
UpdateBackupScheduleData,
|
||||||
|
UpdateBackupScheduleResponses,
|
||||||
|
UpdateVolumeData,
|
||||||
|
UpdateVolumeErrors,
|
||||||
|
UpdateVolumeResponses,
|
||||||
} from "./types.gen";
|
} from "./types.gen";
|
||||||
import { client as _heyApiClient } from "./client.gen";
|
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<
|
||||||
TData,
|
TData,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
> & {
|
> & {
|
||||||
@@ -107,7 +107,7 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
|
|||||||
* Register a new user
|
* Register a new user
|
||||||
*/
|
*/
|
||||||
export const register = <ThrowOnError extends boolean = false>(options?: Options<RegisterData, ThrowOnError>) => {
|
export const register = <ThrowOnError extends boolean = false>(options?: Options<RegisterData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).post<RegisterResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<RegisterResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/auth/register",
|
url: "/api/v1/auth/register",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -121,7 +121,7 @@ export const register = <ThrowOnError extends boolean = false>(options?: Options
|
|||||||
* Login with username and password
|
* Login with username and password
|
||||||
*/
|
*/
|
||||||
export const login = <ThrowOnError extends boolean = false>(options?: Options<LoginData, ThrowOnError>) => {
|
export const login = <ThrowOnError extends boolean = false>(options?: Options<LoginData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).post<LoginResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<LoginResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/auth/login",
|
url: "/api/v1/auth/login",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -135,7 +135,7 @@ export const login = <ThrowOnError extends boolean = false>(options?: Options<Lo
|
|||||||
* Logout current user
|
* Logout current user
|
||||||
*/
|
*/
|
||||||
export const logout = <ThrowOnError extends boolean = false>(options?: Options<LogoutData, ThrowOnError>) => {
|
export const logout = <ThrowOnError extends boolean = false>(options?: Options<LogoutData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).post<LogoutResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<LogoutResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/auth/logout",
|
url: "/api/v1/auth/logout",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -145,7 +145,7 @@ export const logout = <ThrowOnError extends boolean = false>(options?: Options<L
|
|||||||
* Get current authenticated user
|
* Get current authenticated user
|
||||||
*/
|
*/
|
||||||
export const getMe = <ThrowOnError extends boolean = false>(options?: Options<GetMeData, ThrowOnError>) => {
|
export const getMe = <ThrowOnError extends boolean = false>(options?: Options<GetMeData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).get<GetMeResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<GetMeResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/auth/me",
|
url: "/api/v1/auth/me",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -155,7 +155,7 @@ export const getMe = <ThrowOnError extends boolean = false>(options?: Options<Ge
|
|||||||
* Get authentication system status
|
* Get authentication system status
|
||||||
*/
|
*/
|
||||||
export const getStatus = <ThrowOnError extends boolean = false>(options?: Options<GetStatusData, ThrowOnError>) => {
|
export const getStatus = <ThrowOnError extends boolean = false>(options?: Options<GetStatusData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).get<GetStatusResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<GetStatusResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/auth/status",
|
url: "/api/v1/auth/status",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -167,7 +167,7 @@ export const getStatus = <ThrowOnError extends boolean = false>(options?: Option
|
|||||||
export const changePassword = <ThrowOnError extends boolean = false>(
|
export const changePassword = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<ChangePasswordData, ThrowOnError>,
|
options?: Options<ChangePasswordData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<ChangePasswordResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<ChangePasswordResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/auth/change-password",
|
url: "/api/v1/auth/change-password",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -181,7 +181,7 @@ export const changePassword = <ThrowOnError extends boolean = false>(
|
|||||||
* List all volumes
|
* List all volumes
|
||||||
*/
|
*/
|
||||||
export const listVolumes = <ThrowOnError extends boolean = false>(options?: Options<ListVolumesData, ThrowOnError>) => {
|
export const listVolumes = <ThrowOnError extends boolean = false>(options?: Options<ListVolumesData, ThrowOnError>) => {
|
||||||
return (options?.client ?? _heyApiClient).get<ListVolumesResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<ListVolumesResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes",
|
url: "/api/v1/volumes",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -193,7 +193,7 @@ export const listVolumes = <ThrowOnError extends boolean = false>(options?: Opti
|
|||||||
export const createVolume = <ThrowOnError extends boolean = false>(
|
export const createVolume = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<CreateVolumeData, ThrowOnError>,
|
options?: Options<CreateVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<CreateVolumeResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<CreateVolumeResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes",
|
url: "/api/v1/volumes",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -209,7 +209,7 @@ export const createVolume = <ThrowOnError extends boolean = false>(
|
|||||||
export const testConnection = <ThrowOnError extends boolean = false>(
|
export const testConnection = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<TestConnectionData, ThrowOnError>,
|
options?: Options<TestConnectionData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<TestConnectionResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<TestConnectionResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/test-connection",
|
url: "/api/v1/volumes/test-connection",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -225,7 +225,7 @@ export const testConnection = <ThrowOnError extends boolean = false>(
|
|||||||
export const deleteVolume = <ThrowOnError extends boolean = false>(
|
export const deleteVolume = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<DeleteVolumeData, ThrowOnError>,
|
options: Options<DeleteVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}",
|
url: "/api/v1/volumes/{name}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -235,7 +235,7 @@ export const deleteVolume = <ThrowOnError extends boolean = false>(
|
|||||||
* Get a volume by name
|
* Get a volume by name
|
||||||
*/
|
*/
|
||||||
export const getVolume = <ThrowOnError extends boolean = false>(options: Options<GetVolumeData, ThrowOnError>) => {
|
export const getVolume = <ThrowOnError extends boolean = false>(options: Options<GetVolumeData, ThrowOnError>) => {
|
||||||
return (options.client ?? _heyApiClient).get<GetVolumeResponses, GetVolumeErrors, ThrowOnError>({
|
return (options.client ?? client).get<GetVolumeResponses, GetVolumeErrors, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}",
|
url: "/api/v1/volumes/{name}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -247,7 +247,7 @@ export const getVolume = <ThrowOnError extends boolean = false>(options: Options
|
|||||||
export const updateVolume = <ThrowOnError extends boolean = false>(
|
export const updateVolume = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<UpdateVolumeData, ThrowOnError>,
|
options: Options<UpdateVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).put<UpdateVolumeResponses, UpdateVolumeErrors, ThrowOnError>({
|
return (options.client ?? client).put<UpdateVolumeResponses, UpdateVolumeErrors, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}",
|
url: "/api/v1/volumes/{name}",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -263,7 +263,7 @@ export const updateVolume = <ThrowOnError extends boolean = false>(
|
|||||||
export const getContainersUsingVolume = <ThrowOnError extends boolean = false>(
|
export const getContainersUsingVolume = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<GetContainersUsingVolumeData, ThrowOnError>,
|
options: Options<GetContainersUsingVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<
|
return (options.client ?? client).get<
|
||||||
GetContainersUsingVolumeResponses,
|
GetContainersUsingVolumeResponses,
|
||||||
GetContainersUsingVolumeErrors,
|
GetContainersUsingVolumeErrors,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
@@ -277,7 +277,7 @@ export const getContainersUsingVolume = <ThrowOnError extends boolean = false>(
|
|||||||
* Mount a volume
|
* Mount a volume
|
||||||
*/
|
*/
|
||||||
export const mountVolume = <ThrowOnError extends boolean = false>(options: Options<MountVolumeData, ThrowOnError>) => {
|
export const mountVolume = <ThrowOnError extends boolean = false>(options: Options<MountVolumeData, ThrowOnError>) => {
|
||||||
return (options.client ?? _heyApiClient).post<MountVolumeResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).post<MountVolumeResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}/mount",
|
url: "/api/v1/volumes/{name}/mount",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -289,7 +289,7 @@ export const mountVolume = <ThrowOnError extends boolean = false>(options: Optio
|
|||||||
export const unmountVolume = <ThrowOnError extends boolean = false>(
|
export const unmountVolume = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<UnmountVolumeData, ThrowOnError>,
|
options: Options<UnmountVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).post<UnmountVolumeResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).post<UnmountVolumeResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}/unmount",
|
url: "/api/v1/volumes/{name}/unmount",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -301,7 +301,7 @@ export const unmountVolume = <ThrowOnError extends boolean = false>(
|
|||||||
export const healthCheckVolume = <ThrowOnError extends boolean = false>(
|
export const healthCheckVolume = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<HealthCheckVolumeData, ThrowOnError>,
|
options: Options<HealthCheckVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).post<HealthCheckVolumeResponses, HealthCheckVolumeErrors, ThrowOnError>({
|
return (options.client ?? client).post<HealthCheckVolumeResponses, HealthCheckVolumeErrors, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}/health-check",
|
url: "/api/v1/volumes/{name}/health-check",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -311,7 +311,7 @@ export const healthCheckVolume = <ThrowOnError extends boolean = false>(
|
|||||||
* List files in a volume directory
|
* List files in a volume directory
|
||||||
*/
|
*/
|
||||||
export const listFiles = <ThrowOnError extends boolean = false>(options: Options<ListFilesData, ThrowOnError>) => {
|
export const listFiles = <ThrowOnError extends boolean = false>(options: Options<ListFilesData, ThrowOnError>) => {
|
||||||
return (options.client ?? _heyApiClient).get<ListFilesResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<ListFilesResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/{name}/files",
|
url: "/api/v1/volumes/{name}/files",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -323,7 +323,7 @@ export const listFiles = <ThrowOnError extends boolean = false>(options: Options
|
|||||||
export const browseFilesystem = <ThrowOnError extends boolean = false>(
|
export const browseFilesystem = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<BrowseFilesystemData, ThrowOnError>,
|
options?: Options<BrowseFilesystemData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).get<BrowseFilesystemResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<BrowseFilesystemResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/volumes/filesystem/browse",
|
url: "/api/v1/volumes/filesystem/browse",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -335,7 +335,7 @@ export const browseFilesystem = <ThrowOnError extends boolean = false>(
|
|||||||
export const listRepositories = <ThrowOnError extends boolean = false>(
|
export const listRepositories = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<ListRepositoriesData, ThrowOnError>,
|
options?: Options<ListRepositoriesData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).get<ListRepositoriesResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<ListRepositoriesResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories",
|
url: "/api/v1/repositories",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -347,7 +347,7 @@ export const listRepositories = <ThrowOnError extends boolean = false>(
|
|||||||
export const createRepository = <ThrowOnError extends boolean = false>(
|
export const createRepository = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<CreateRepositoryData, ThrowOnError>,
|
options?: Options<CreateRepositoryData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<CreateRepositoryResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<CreateRepositoryResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories",
|
url: "/api/v1/repositories",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -363,7 +363,7 @@ export const createRepository = <ThrowOnError extends boolean = false>(
|
|||||||
export const listRcloneRemotes = <ThrowOnError extends boolean = false>(
|
export const listRcloneRemotes = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<ListRcloneRemotesData, ThrowOnError>,
|
options?: Options<ListRcloneRemotesData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).get<ListRcloneRemotesResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<ListRcloneRemotesResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/rclone-remotes",
|
url: "/api/v1/repositories/rclone-remotes",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -375,7 +375,7 @@ export const listRcloneRemotes = <ThrowOnError extends boolean = false>(
|
|||||||
export const deleteRepository = <ThrowOnError extends boolean = false>(
|
export const deleteRepository = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<DeleteRepositoryData, ThrowOnError>,
|
options: Options<DeleteRepositoryData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).delete<DeleteRepositoryResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).delete<DeleteRepositoryResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}",
|
url: "/api/v1/repositories/{name}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -387,7 +387,7 @@ export const deleteRepository = <ThrowOnError extends boolean = false>(
|
|||||||
export const getRepository = <ThrowOnError extends boolean = false>(
|
export const getRepository = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<GetRepositoryData, ThrowOnError>,
|
options: Options<GetRepositoryData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<GetRepositoryResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<GetRepositoryResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}",
|
url: "/api/v1/repositories/{name}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -399,7 +399,7 @@ export const getRepository = <ThrowOnError extends boolean = false>(
|
|||||||
export const listSnapshots = <ThrowOnError extends boolean = false>(
|
export const listSnapshots = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<ListSnapshotsData, ThrowOnError>,
|
options: Options<ListSnapshotsData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<ListSnapshotsResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<ListSnapshotsResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}/snapshots",
|
url: "/api/v1/repositories/{name}/snapshots",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -411,7 +411,7 @@ export const listSnapshots = <ThrowOnError extends boolean = false>(
|
|||||||
export const getSnapshotDetails = <ThrowOnError extends boolean = false>(
|
export const getSnapshotDetails = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<GetSnapshotDetailsData, ThrowOnError>,
|
options: Options<GetSnapshotDetailsData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<GetSnapshotDetailsResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<GetSnapshotDetailsResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}/snapshots/{snapshotId}",
|
url: "/api/v1/repositories/{name}/snapshots/{snapshotId}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -423,7 +423,7 @@ export const getSnapshotDetails = <ThrowOnError extends boolean = false>(
|
|||||||
export const listSnapshotFiles = <ThrowOnError extends boolean = false>(
|
export const listSnapshotFiles = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<ListSnapshotFilesData, ThrowOnError>,
|
options: Options<ListSnapshotFilesData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<ListSnapshotFilesResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<ListSnapshotFilesResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}/snapshots/{snapshotId}/files",
|
url: "/api/v1/repositories/{name}/snapshots/{snapshotId}/files",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -435,7 +435,7 @@ export const listSnapshotFiles = <ThrowOnError extends boolean = false>(
|
|||||||
export const restoreSnapshot = <ThrowOnError extends boolean = false>(
|
export const restoreSnapshot = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<RestoreSnapshotData, ThrowOnError>,
|
options: Options<RestoreSnapshotData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).post<RestoreSnapshotResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).post<RestoreSnapshotResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}/restore",
|
url: "/api/v1/repositories/{name}/restore",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -451,7 +451,7 @@ export const restoreSnapshot = <ThrowOnError extends boolean = false>(
|
|||||||
export const doctorRepository = <ThrowOnError extends boolean = false>(
|
export const doctorRepository = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<DoctorRepositoryData, ThrowOnError>,
|
options: Options<DoctorRepositoryData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).post<DoctorRepositoryResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).post<DoctorRepositoryResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/repositories/{name}/doctor",
|
url: "/api/v1/repositories/{name}/doctor",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -463,7 +463,7 @@ export const doctorRepository = <ThrowOnError extends boolean = false>(
|
|||||||
export const listBackupSchedules = <ThrowOnError extends boolean = false>(
|
export const listBackupSchedules = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<ListBackupSchedulesData, ThrowOnError>,
|
options?: Options<ListBackupSchedulesData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).get<ListBackupSchedulesResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<ListBackupSchedulesResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups",
|
url: "/api/v1/backups",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -475,7 +475,7 @@ export const listBackupSchedules = <ThrowOnError extends boolean = false>(
|
|||||||
export const createBackupSchedule = <ThrowOnError extends boolean = false>(
|
export const createBackupSchedule = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<CreateBackupScheduleData, ThrowOnError>,
|
options?: Options<CreateBackupScheduleData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<CreateBackupScheduleResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<CreateBackupScheduleResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups",
|
url: "/api/v1/backups",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -491,7 +491,7 @@ export const createBackupSchedule = <ThrowOnError extends boolean = false>(
|
|||||||
export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(
|
export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<DeleteBackupScheduleData, ThrowOnError>,
|
options: Options<DeleteBackupScheduleData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).delete<DeleteBackupScheduleResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).delete<DeleteBackupScheduleResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups/{scheduleId}",
|
url: "/api/v1/backups/{scheduleId}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -503,7 +503,7 @@ export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(
|
|||||||
export const getBackupSchedule = <ThrowOnError extends boolean = false>(
|
export const getBackupSchedule = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<GetBackupScheduleData, ThrowOnError>,
|
options: Options<GetBackupScheduleData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<GetBackupScheduleResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<GetBackupScheduleResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups/{scheduleId}",
|
url: "/api/v1/backups/{scheduleId}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -515,7 +515,7 @@ export const getBackupSchedule = <ThrowOnError extends boolean = false>(
|
|||||||
export const updateBackupSchedule = <ThrowOnError extends boolean = false>(
|
export const updateBackupSchedule = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<UpdateBackupScheduleData, ThrowOnError>,
|
options: Options<UpdateBackupScheduleData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).patch<UpdateBackupScheduleResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).patch<UpdateBackupScheduleResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups/{scheduleId}",
|
url: "/api/v1/backups/{scheduleId}",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -531,7 +531,7 @@ export const updateBackupSchedule = <ThrowOnError extends boolean = false>(
|
|||||||
export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>(
|
export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<GetBackupScheduleForVolumeData, ThrowOnError>,
|
options: Options<GetBackupScheduleForVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).get<GetBackupScheduleForVolumeResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).get<GetBackupScheduleForVolumeResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups/volume/{volumeId}",
|
url: "/api/v1/backups/volume/{volumeId}",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -543,7 +543,7 @@ export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>
|
|||||||
export const runBackupNow = <ThrowOnError extends boolean = false>(
|
export const runBackupNow = <ThrowOnError extends boolean = false>(
|
||||||
options: Options<RunBackupNowData, ThrowOnError>,
|
options: Options<RunBackupNowData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options.client ?? _heyApiClient).post<RunBackupNowResponses, unknown, ThrowOnError>({
|
return (options.client ?? client).post<RunBackupNowResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/backups/{scheduleId}/run",
|
url: "/api/v1/backups/{scheduleId}/run",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -553,7 +553,7 @@ export const runBackupNow = <ThrowOnError extends boolean = false>(
|
|||||||
* Stop a backup that is currently in progress
|
* Stop a backup that is currently in progress
|
||||||
*/
|
*/
|
||||||
export const stopBackup = <ThrowOnError extends boolean = false>(options: Options<StopBackupData, ThrowOnError>) => {
|
export const stopBackup = <ThrowOnError extends boolean = false>(options: Options<StopBackupData, ThrowOnError>) => {
|
||||||
return (options.client ?? _heyApiClient).post<StopBackupResponses, StopBackupErrors, ThrowOnError>({
|
return (options.client ?? client).post<StopBackupResponses, StopBackupErrors, ThrowOnError>({
|
||||||
url: "/api/v1/backups/{scheduleId}/stop",
|
url: "/api/v1/backups/{scheduleId}/stop",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -565,7 +565,7 @@ export const stopBackup = <ThrowOnError extends boolean = false>(options: Option
|
|||||||
export const getSystemInfo = <ThrowOnError extends boolean = false>(
|
export const getSystemInfo = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<GetSystemInfoData, ThrowOnError>,
|
options?: Options<GetSystemInfoData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).get<GetSystemInfoResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).get<GetSystemInfoResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/system/info",
|
url: "/api/v1/system/info",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -577,7 +577,7 @@ export const getSystemInfo = <ThrowOnError extends boolean = false>(
|
|||||||
export const downloadResticPassword = <ThrowOnError extends boolean = false>(
|
export const downloadResticPassword = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<DownloadResticPasswordData, ThrowOnError>,
|
options?: Options<DownloadResticPasswordData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<DownloadResticPasswordResponses, unknown, ThrowOnError>({
|
return (options?.client ?? client).post<DownloadResticPasswordResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/v1/system/restic-password",
|
url: "/api/v1/system/restic-password",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
export type ClientOptions = {
|
||||||
|
baseUrl: "http://192.168.2.42:4096" | (string & {});
|
||||||
|
};
|
||||||
|
|
||||||
export type RegisterData = {
|
export type RegisterData = {
|
||||||
body?: {
|
body?: {
|
||||||
password: string;
|
password: string;
|
||||||
@@ -1672,7 +1676,3 @@ export type DownloadResticPasswordResponses = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DownloadResticPasswordResponse = DownloadResticPasswordResponses[keyof DownloadResticPasswordResponses];
|
export type DownloadResticPasswordResponse = DownloadResticPasswordResponses[keyof DownloadResticPasswordResponses];
|
||||||
|
|
||||||
export type ClientOptions = {
|
|
||||||
baseUrl: "http://192.168.2.42:4096" | (string & {});
|
|
||||||
};
|
|
||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "~/components/ui/breadcrumb";
|
} from "~/client/components/ui/breadcrumb";
|
||||||
import { useBreadcrumbs } from "~/lib/breadcrumbs";
|
import { useBreadcrumbs } from "~/client/lib/breadcrumbs";
|
||||||
|
|
||||||
export function AppBreadcrumb() {
|
export function AppBreadcrumb() {
|
||||||
const breadcrumbs = useBreadcrumbs();
|
const breadcrumbs = useBreadcrumbs();
|
||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "~/components/ui/sidebar";
|
} from "~/client/components/ui/sidebar";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/client/components/ui/tooltip";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
@@ -2,12 +2,12 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { useId } from "react";
|
import { useId } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { createRepositoryMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { parseError } from "~/lib/errors";
|
|
||||||
import { CreateRepositoryForm } from "./create-repository-form";
|
import { CreateRepositoryForm } from "./create-repository-form";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
|
||||||
import { ScrollArea } from "./ui/scroll-area";
|
import { ScrollArea } from "./ui/scroll-area";
|
||||||
|
import { createRepositoryMutation } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||||
import { COMPRESSION_MODES, repositoryConfigSchema } from "@ironmount/schemas/restic";
|
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { cn, slugify } from "~/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";
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./ui/form";
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./ui/form";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
||||||
import { listRcloneRemotesOptions } from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Alert, AlertDescription } from "./ui/alert";
|
import { Alert, AlertDescription } from "./ui/alert";
|
||||||
import { ExternalLink } from "lucide-react";
|
import { ExternalLink } from "lucide-react";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
import { useSystemInfo } from "~/hooks/use-system-info";
|
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||||
|
import { COMPRESSION_MODES, repositoryConfigSchema } from "~/schemas/restic";
|
||||||
|
import { listRcloneRemotesOptions } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export const formSchema = type({
|
export const formSchema = type({
|
||||||
name: "2<=string<=32",
|
name: "2<=string<=32",
|
||||||
@@ -61,17 +61,17 @@ export const CreateRepositoryForm = ({
|
|||||||
const { watch } = form;
|
const { watch } = form;
|
||||||
|
|
||||||
const watchedBackend = watch("backend");
|
const watchedBackend = watch("backend");
|
||||||
const watchedName = watch("name");
|
|
||||||
|
|
||||||
const { data: rcloneRemotes, isLoading: isLoadingRemotes } = useQuery({
|
const { data: rcloneRemotes, isLoading: isLoadingRemotes } = useQuery({
|
||||||
...listRcloneRemotesOptions(),
|
...listRcloneRemotesOptions(),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (watchedBackend && watchedBackend in defaultValuesForType) {
|
form.reset({
|
||||||
form.reset({ name: watchedName, ...defaultValuesForType[watchedBackend as keyof typeof defaultValuesForType] });
|
name: form.getValues().name,
|
||||||
}
|
...defaultValuesForType[watchedBackend as keyof typeof defaultValuesForType],
|
||||||
}, [watchedBackend, watchedName, form]);
|
});
|
||||||
|
}, [watchedBackend, form]);
|
||||||
|
|
||||||
const { capabilities } = useSystemInfo();
|
const { capabilities } = useSystemInfo();
|
||||||
|
|
||||||
@@ -2,12 +2,12 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { useId } from "react";
|
import { useId } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { createVolumeMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { parseError } from "~/lib/errors";
|
|
||||||
import { CreateVolumeForm } from "./create-volume-form";
|
import { CreateVolumeForm } from "./create-volume-form";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
|
||||||
import { ScrollArea } from "./ui/scroll-area";
|
import { ScrollArea } from "./ui/scroll-area";
|
||||||
|
import { createVolumeMutation } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||||
import { volumeConfigSchema } from "@ironmount/schemas";
|
|
||||||
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, 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 { testConnectionMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { cn, slugify } from "~/client/lib/utils";
|
||||||
import { cn, slugify } from "~/lib/utils";
|
|
||||||
import { deepClean } from "~/utils/object";
|
import { deepClean } from "~/utils/object";
|
||||||
import { DirectoryBrowser } from "./directory-browser";
|
import { DirectoryBrowser } from "./directory-browser";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./ui/form";
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./ui/form";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
||||||
|
import { volumeConfigSchema } from "~/schemas/volumes";
|
||||||
|
import { testConnectionMutation } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export const formSchema = type({
|
export const formSchema = type({
|
||||||
name: "2<=string<=32",
|
name: "2<=string<=32",
|
||||||
@@ -50,13 +50,15 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
|||||||
const { watch, getValues } = form;
|
const { watch, getValues } = form;
|
||||||
|
|
||||||
const watchedBackend = watch("backend");
|
const watchedBackend = watch("backend");
|
||||||
const watchedName = watch("name");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === "create") {
|
if (mode === "create") {
|
||||||
form.reset({ name: watchedName, ...defaultValuesForType[watchedBackend as keyof typeof defaultValuesForType] });
|
form.reset({
|
||||||
|
name: form.getValues().name,
|
||||||
|
...defaultValuesForType[watchedBackend as keyof typeof defaultValuesForType],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [watchedBackend, watchedName, form.reset, mode]);
|
}, [watchedBackend, form, mode]);
|
||||||
|
|
||||||
const [testMessage, setTestMessage] = useState<{ success: boolean; message: string } | null>(null);
|
const [testMessage, setTestMessage] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
|
|
||||||
@@ -141,19 +143,17 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="path"
|
name="path"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
const [showBrowser, setShowBrowser] = useState(!field.value || field.value === "/");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Directory Path</FormLabel>
|
<FormLabel>Directory Path</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
{!showBrowser && field.value ? (
|
{field.value ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex-1 border rounded-md p-3 bg-muted/50">
|
<div className="flex-1 border rounded-md p-3 bg-muted/50">
|
||||||
<div className="text-xs font-medium text-muted-foreground mb-1">Selected path:</div>
|
<div className="text-xs font-medium text-muted-foreground mb-1">Selected path:</div>
|
||||||
<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={() => setShowBrowser(true)}>
|
<Button type="button" variant="outline" size="sm" onClick={() => field.onChange("")}>
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { browseFilesystemOptions } from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import { FileTree, type FileEntry } from "./file-tree";
|
import { FileTree, type FileEntry } from "./file-tree";
|
||||||
import { ScrollArea } from "./ui/scroll-area";
|
import { ScrollArea } from "./ui/scroll-area";
|
||||||
|
import { browseFilesystemOptions } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSelectPath: (path: string) => void;
|
onSelectPath: (path: string) => void;
|
||||||
@@ -17,7 +17,7 @@ export function EmptyState(props: EmptyStateProps) {
|
|||||||
<div className="absolute inset-0 animate-pulse">
|
<div className="absolute inset-0 animate-pulse">
|
||||||
<div className="w-32 h-32 rounded-full bg-primary/10 blur-2xl" />
|
<div className="w-32 h-32 rounded-full bg-primary/10 blur-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex items-center justify-center w-32 h-32 rounded-full bg-gradient-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
<div className="relative flex items-center justify-center w-32 h-32 rounded-full bg-linear-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
||||||
<Cicon className="w-16 h-16 text-primary/70" strokeWidth={1.5} />
|
<Cicon className="w-16 h-16 text-primary/70" strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
import { ChevronDown, ChevronRight, File as FileIcon, Folder as FolderIcon, FolderOpen, Loader2 } from "lucide-react";
|
import { ChevronDown, ChevronRight, File as FileIcon, Folder as FolderIcon, FolderOpen, Loader2 } from "lucide-react";
|
||||||
import { memo, type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
import { memo, type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Checkbox } from "~/components/ui/checkbox";
|
import { Checkbox } from "~/client/components/ui/checkbox";
|
||||||
|
|
||||||
const NODE_PADDING_LEFT = 12;
|
const NODE_PADDING_LEFT = 12;
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
interface GridBackgroundProps {
|
interface GridBackgroundProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -12,9 +12,9 @@ export function GridBackground({ children, className, containerClassName }: Grid
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative min-h-full w-full overflow-x-hidden",
|
"relative min-h-full w-full overflow-x-hidden",
|
||||||
"[background-size:20px_20px] sm:[background-size:40px_40px]",
|
"bg-size-[20px_20px] sm:bg-size-[40px_40px]",
|
||||||
"[background-image:linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)]",
|
"bg-[linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)]",
|
||||||
"dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]",
|
"dark:bg-[linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]",
|
||||||
containerClassName,
|
containerClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -2,7 +2,6 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { LifeBuoy } from "lucide-react";
|
import { LifeBuoy } from "lucide-react";
|
||||||
import { Outlet, redirect, useNavigate } from "react-router";
|
import { Outlet, redirect, useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { logoutMutation } from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import { appContext } from "~/context";
|
import { appContext } from "~/context";
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
import type { Route } from "./+types/layout";
|
import type { Route } from "./+types/layout";
|
||||||
@@ -11,6 +10,7 @@ import { GridBackground } from "./grid-background";
|
|||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { SidebarProvider, SidebarTrigger } from "./ui/sidebar";
|
import { SidebarProvider, SidebarTrigger } from "./ui/sidebar";
|
||||||
import { AppSidebar } from "./app-sidebar";
|
import { AppSidebar } from "./app-sidebar";
|
||||||
|
import { logoutMutation } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export const clientMiddleware = [authMiddleware];
|
export const clientMiddleware = [authMiddleware];
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export default function Layout({ loaderData }: Route.ComponentProps) {
|
|||||||
<SidebarProvider defaultOpen={true}>
|
<SidebarProvider defaultOpen={true}>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<div className="w-full relative flex flex-col h-screen overflow-hidden">
|
<div className="w-full relative flex flex-col h-screen overflow-hidden">
|
||||||
<header className="z-50 bg-card-header border-b border-border/50 flex-shrink-0">
|
<header className="z-50 bg-card-header border-b border-border/50 shrink-0">
|
||||||
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-8 mx-auto container">
|
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-8 mx-auto container">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Switch } from "./ui/switch";
|
import { Switch } from "./ui/switch";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { RepositoryBackend } from "@ironmount/schemas/restic";
|
|
||||||
import { Database, HardDrive, Cloud } from "lucide-react";
|
import { Database, HardDrive, Cloud } from "lucide-react";
|
||||||
|
import type { RepositoryBackend } from "~/schemas/restic";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
backend: RepositoryBackend;
|
backend: RepositoryBackend;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Calendar, Clock, Database, FolderTree, HardDrive } from "lucide-react";
|
import { Calendar, Clock, Database, FolderTree, HardDrive } from "lucide-react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import type { ListSnapshotsResponse } from "~/api-client/types.gen";
|
import { ByteSize } from "~/client/components/bytes-size";
|
||||||
import { ByteSize } from "~/components/bytes-size";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
|
||||||
import { formatDuration } from "~/utils/utils";
|
import { formatDuration } from "~/utils/utils";
|
||||||
|
import type { ListSnapshotsResponse } from "../api-client";
|
||||||
|
|
||||||
type Snapshot = ListSnapshotsResponse[number];
|
type Snapshot = ListSnapshotsResponse[number];
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { VolumeStatus } from "~/lib/types";
|
import type { VolumeStatus } from "~/client/lib/types";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
|
|
||||||
export const StatusDot = ({ status }: { status: VolumeStatus }) => {
|
export const StatusDot = ({ status }: { status: VolumeStatus }) => {
|
||||||
@@ -38,10 +38,7 @@ export const StatusDot = ({ status }: { status: VolumeStatus }) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span
|
<span className={cn("relative inline-flex size-3 rounded-full", `${statusMapping.color}`)} />
|
||||||
aria-label={status}
|
|
||||||
className={cn("relative inline-flex size-3 rounded-full", `${statusMapping.color}`)}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
import { buttonVariants } from "~/components/ui/button";
|
import { buttonVariants } from "~/client/components/ui/button";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
||||||
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
@@ -2,7 +2,7 @@ import type * as React from "react";
|
|||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||||
92
app/client/components/ui/breadcrumb.tsx
Normal file
92
app/client/components/ui/breadcrumb.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
|
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||||
|
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
|
return (
|
||||||
|
<ol
|
||||||
|
data-slot="breadcrumb-list"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm wrap-break-words sm:gap-2.5",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
|
return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbLink({
|
||||||
|
asChild,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"a"> & {
|
||||||
|
asChild?: boolean;
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "a";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp data-slot="breadcrumb-link" className={cn("hover:text-foreground transition-colors", className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="breadcrumb-page"
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
className={cn("text-foreground font-normal", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
data-slot="breadcrumb-separator"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? <ChevronRight />}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="breadcrumb-ellipsis"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("flex size-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="size-4" />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
BreadcrumbEllipsis,
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex cursor-pointer uppercase rounded-sm items-center justify-center gap-2 whitespace-nowrap text-xs font-semibold tracking-wide transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-ring border-0",
|
"inline-flex cursor-pointer uppercase rounded-sm items-center justify-center gap-2 whitespace-nowrap text-xs font-semibold tracking-wide transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-ring border-0",
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Card({ className, children, ...props }: React.ComponentProps<"div">) {
|
function Card({ className, children, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as RechartsPrimitive from "recharts";
|
import * as RechartsPrimitive from "recharts";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
const THEMES = { light: "", dark: ".dark" } as const;
|
const THEMES = { light: "", dark: ".dark" } as const;
|
||||||
27
app/client/components/ui/checkbox.tsx
Normal file
27
app/client/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
data-slot="checkbox"
|
||||||
|
className={cn(
|
||||||
|
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
data-slot="checkbox-indicator"
|
||||||
|
className="grid place-content-center text-current transition-none"
|
||||||
|
>
|
||||||
|
<CheckIcon className="size-3.5" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Checkbox };
|
||||||
121
app/client/components/ui/dialog.tsx
Normal file
121
app/client/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||||
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal data-slot="dialog-portal">
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
data-slot="dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
data-slot="dialog-close"
|
||||||
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot="dialog-title"
|
||||||
|
className={cn("text-lg leading-none font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot="dialog-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
import type * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
type FieldValues,
|
type FieldValues,
|
||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/client/components/ui/label";
|
||||||
|
|
||||||
const Form = FormProvider;
|
const Form = FormProvider;
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
21
app/client/components/ui/label.tsx
Normal file
21
app/client/components/ui/label.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type * as React from "react";
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
data-slot="label"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Label };
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Progress({ className, value, ...props }: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
function Progress({ className, value, ...props }: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
||||||
return (
|
return (
|
||||||
46
app/client/components/ui/scroll-area.tsx
Normal file
46
app/client/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
|
||||||
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
data-slot="scroll-area-viewport"
|
||||||
|
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScrollBar({
|
||||||
|
className,
|
||||||
|
orientation = "vertical",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
data-slot="scroll-area-scrollbar"
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"flex touch-none p-px transition-colors select-none",
|
||||||
|
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
|
||||||
|
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||||
|
data-slot="scroll-area-thumb"
|
||||||
|
className="bg-border relative flex-1 rounded-full"
|
||||||
|
/>
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar };
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Select({
|
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectGroup({
|
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
||||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectValue({
|
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +77,7 @@ function SelectContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectLabel({
|
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Label
|
<SelectPrimitive.Label
|
||||||
data-slot="select-label"
|
data-slot="select-label"
|
||||||
@@ -96,11 +87,7 @@ function SelectLabel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectItem({
|
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
@@ -120,10 +107,7 @@ function SelectItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectSeparator({
|
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Separator
|
<SelectPrimitive.Separator
|
||||||
data-slot="select-separator"
|
data-slot="select-separator"
|
||||||
@@ -133,17 +117,11 @@ function SelectSeparator({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollUpButton({
|
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.ScrollUpButton
|
<SelectPrimitive.ScrollUpButton
|
||||||
data-slot="select-scroll-up-button"
|
data-slot="select-scroll-up-button"
|
||||||
className={cn(
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||||
"flex cursor-default items-center justify-center py-1",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon className="size-4" />
|
<ChevronUpIcon className="size-4" />
|
||||||
@@ -158,10 +136,7 @@ function SelectScrollDownButton({
|
|||||||
return (
|
return (
|
||||||
<SelectPrimitive.ScrollDownButton
|
<SelectPrimitive.ScrollDownButton
|
||||||
data-slot="select-scroll-down-button"
|
data-slot="select-scroll-down-button"
|
||||||
className={cn(
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||||
"flex cursor-default items-center justify-center py-1",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
26
app/client/components/ui/separator.tsx
Normal file
26
app/client/components/ui/separator.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Separator({
|
||||||
|
className,
|
||||||
|
orientation = "horizontal",
|
||||||
|
decorative = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
data-slot="separator"
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Separator };
|
||||||
103
app/client/components/ui/sheet.tsx
Normal file
103
app/client/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type * as React from "react";
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||||
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||||
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||||
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||||
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
data-slot="sheet-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
side = "right",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
|
side?: "top" | "right" | "bottom" | "left";
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
data-slot="sheet-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
side === "right" &&
|
||||||
|
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||||
|
side === "left" &&
|
||||||
|
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||||
|
side === "top" &&
|
||||||
|
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||||
|
side === "bottom" &&
|
||||||
|
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
data-slot="sheet-title"
|
||||||
|
className={cn("text-foreground font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
data-slot="sheet-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
|
||||||
@@ -5,14 +5,14 @@ import { Slot } from "@radix-ui/react-slot";
|
|||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { PanelLeftIcon } from "lucide-react";
|
import { PanelLeftIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useIsMobile } from "~/hooks/use-mobile";
|
import { useIsMobile } from "~/client/hooks/use-mobile";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Separator } from "~/components/ui/separator";
|
import { Separator } from "~/client/components/ui/separator";
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/components/ui/sheet";
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/client/components/ui/sheet";
|
||||||
import { Skeleton } from "~/components/ui/skeleton";
|
import { Skeleton } from "~/client/components/ui/skeleton";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/client/components/ui/tooltip";
|
||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||||
7
app/client/components/ui/skeleton.tsx
Normal file
7
app/client/components/ui/skeleton.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="skeleton" className={cn("bg-accent animate-pulse rounded-md", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton };
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||||
return (
|
return (
|
||||||
73
app/client/components/ui/table.tsx
Normal file
73
app/client/components/ui/table.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||||
|
return (
|
||||||
|
<div data-slot="table-container" className="relative w-full overflow-x-auto">
|
||||||
|
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||||
|
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||||
|
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||||
|
return (
|
||||||
|
<tfoot
|
||||||
|
data-slot="table-footer"
|
||||||
|
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
data-slot="table-row"
|
||||||
|
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
data-slot="table-head"
|
||||||
|
className={cn(
|
||||||
|
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
data-slot="table-cell"
|
||||||
|
className={cn(
|
||||||
|
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
|
||||||
|
return (
|
||||||
|
<caption data-slot="table-caption" className={cn("text-muted-foreground mt-4 text-sm", className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||||
return <TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />;
|
return <TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
return (
|
return (
|
||||||
46
app/client/components/ui/tooltip.tsx
Normal file
46
app/client/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
|
||||||
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
|
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||||
|
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||||
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TooltipContent({
|
||||||
|
className,
|
||||||
|
sideOffset = 0,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
data-slot="tooltip-content"
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||||
|
</TooltipPrimitive.Content>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { FolderOpen } from "lucide-react";
|
import { FolderOpen } from "lucide-react";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { listFilesOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { FileTree } from "~/client/components/file-tree";
|
||||||
import { FileTree } from "~/components/file-tree";
|
import { listFilesOptions } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
interface FileEntry {
|
interface FileEntry {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { BackendType } from "@ironmount/schemas";
|
|
||||||
import { Cloud, Folder, Server, Share2 } from "lucide-react";
|
import { Cloud, Folder, Server, Share2 } from "lucide-react";
|
||||||
|
import type { BackendType } from "~/schemas/volumes";
|
||||||
|
|
||||||
type VolumeIconProps = {
|
type VolumeIconProps = {
|
||||||
backend: BackendType;
|
backend: BackendType;
|
||||||
19
app/client/hooks/use-mobile.ts
Normal file
19
app/client/hooks/use-mobile.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const MOBILE_BREAKPOINT = 768;
|
||||||
|
|
||||||
|
export function useIsMobile() {
|
||||||
|
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||||
|
const onChange = () => {
|
||||||
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
|
};
|
||||||
|
mql.addEventListener("change", onChange);
|
||||||
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
|
return () => mql.removeEventListener("change", onChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return !!isMobile;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getSystemInfoOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { getSystemInfoOptions } from "../api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export function useSystemInfo() {
|
export function useSystemInfo() {
|
||||||
const { data, isLoading, error } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
@@ -4,7 +4,7 @@ import type {
|
|||||||
GetRepositoryResponse,
|
GetRepositoryResponse,
|
||||||
GetVolumeResponse,
|
GetVolumeResponse,
|
||||||
ListSnapshotsResponse,
|
ListSnapshotsResponse,
|
||||||
} from "~/api-client";
|
} from "../api-client";
|
||||||
|
|
||||||
export type Volume = GetVolumeResponse["volume"];
|
export type Volume = GetVolumeResponse["volume"];
|
||||||
export type StatFs = GetVolumeResponse["statfs"];
|
export type StatFs = GetVolumeResponse["statfs"];
|
||||||
@@ -3,14 +3,14 @@ import { AlertTriangle, Download } 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";
|
||||||
import { downloadResticPasswordMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { AuthLayout } from "~/client/components/auth-layout";
|
||||||
import { AuthLayout } from "~/components/auth-layout";
|
import { Alert, AlertDescription, AlertTitle } from "~/client/components/ui/alert";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Label } from "~/client/components/ui/label";
|
||||||
import { Label } from "~/components/ui/label";
|
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
import type { Route } from "./+types/download-recovery-key";
|
import type { Route } from "./+types/download-recovery-key";
|
||||||
|
import { downloadResticPasswordMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export const clientMiddleware = [authMiddleware];
|
export const clientMiddleware = [authMiddleware];
|
||||||
|
|
||||||
@@ -4,13 +4,13 @@ import { type } from "arktype";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { loginMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { AuthLayout } from "~/client/components/auth-layout";
|
||||||
import { AuthLayout } from "~/components/auth-layout";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/client/components/ui/form";
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Input } from "~/components/ui/input";
|
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
import type { Route } from "./+types/login";
|
import type { Route } from "./+types/login";
|
||||||
|
import { loginMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export const clientMiddleware = [authMiddleware];
|
export const clientMiddleware = [authMiddleware];
|
||||||
|
|
||||||
@@ -4,13 +4,21 @@ import { type } from "arktype";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { registerMutation } from "~/api-client/@tanstack/react-query.gen";
|
import {
|
||||||
import { AuthLayout } from "~/components/auth-layout";
|
Form,
|
||||||
import { Button } from "~/components/ui/button";
|
FormControl,
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
FormDescription,
|
||||||
import { Input } from "~/components/ui/input";
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "~/client/components/ui/form";
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
import type { Route } from "./+types/onboarding";
|
import type { Route } from "./+types/onboarding";
|
||||||
|
import { AuthLayout } from "~/client/components/auth-layout";
|
||||||
|
import { Input } from "~/client/components/ui/input";
|
||||||
|
import { Button } from "~/client/components/ui/button";
|
||||||
|
import { registerMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export const clientMiddleware = [authMiddleware];
|
export const clientMiddleware = [authMiddleware];
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ByteSize, formatBytes } from "~/components/bytes-size";
|
import { ByteSize, formatBytes } from "~/client/components/bytes-size";
|
||||||
import { Card } from "~/components/ui/card";
|
import { Card } from "~/client/components/ui/card";
|
||||||
import { Progress } from "~/components/ui/progress";
|
import { Progress } from "~/client/components/ui/progress";
|
||||||
import { type BackupProgressEvent, useServerEvents } from "~/hooks/use-server-events";
|
import { type BackupProgressEvent, useServerEvents } from "~/client/hooks/use-server-events";
|
||||||
import { formatDuration } from "~/utils/utils";
|
import { formatDuration } from "~/utils/utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip";
|
||||||
|
|
||||||
type BackupStatus = "active" | "paused" | "error" | "in_progress";
|
type BackupStatus = "active" | "paused" | "error" | "in_progress";
|
||||||
|
|
||||||
@@ -7,7 +7,11 @@ export const BackupStatusDot = ({
|
|||||||
enabled,
|
enabled,
|
||||||
hasError,
|
hasError,
|
||||||
isInProgress,
|
isInProgress,
|
||||||
}: { enabled: boolean; hasError?: boolean; isInProgress?: boolean }) => {
|
}: {
|
||||||
|
enabled: boolean;
|
||||||
|
hasError?: boolean;
|
||||||
|
isInProgress?: boolean;
|
||||||
|
}) => {
|
||||||
let status: BackupStatus = "paused";
|
let status: BackupStatus = "paused";
|
||||||
if (isInProgress) {
|
if (isInProgress) {
|
||||||
status = "in_progress";
|
status = "in_progress";
|
||||||
@@ -3,15 +3,23 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { listRepositoriesOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { RepositoryIcon } from "~/components/repository-icon";
|
import { RepositoryIcon } from "~/client/components/repository-icon";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
import {
|
||||||
import { Input } from "~/components/ui/input";
|
Form,
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
FormControl,
|
||||||
import { Textarea } from "~/components/ui/textarea";
|
FormDescription,
|
||||||
import { VolumeFileBrowser } from "~/components/volume-file-browser";
|
FormField,
|
||||||
import type { BackupSchedule, Volume } from "~/lib/types";
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "~/client/components/ui/form";
|
||||||
|
import { Input } from "~/client/components/ui/input";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
|
import { Textarea } from "~/client/components/ui/textarea";
|
||||||
|
import { VolumeFileBrowser } from "~/client/components/volume-file-browser";
|
||||||
|
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
||||||
import { deepClean } from "~/utils/object";
|
import { deepClean } from "~/utils/object";
|
||||||
|
|
||||||
const internalFormSchema = type({
|
const internalFormSchema = type({
|
||||||
@@ -128,7 +136,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(handleSubmit)}
|
onSubmit={form.handleSubmit(handleSubmit)}
|
||||||
className="grid gap-4 xl:grid-cols-[minmax(0,_2.3fr)_minmax(320px,_1fr)]"
|
className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]"
|
||||||
id={formId}
|
id={formId}
|
||||||
>
|
>
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Pencil, Play, Square, Trash2 } from "lucide-react";
|
import { Pencil, Play, Square, Trash2 } from "lucide-react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { OnOff } from "~/components/onoff";
|
import { OnOff } from "~/client/components/onoff";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/client/components/ui/alert-dialog";
|
||||||
import type { BackupSchedule } from "~/lib/types";
|
import type { BackupSchedule } from "~/client/lib/types";
|
||||||
import { BackupProgressCard } from "./backup-progress-card";
|
import { BackupProgressCard } from "./backup-progress-card";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { FileIcon } from "lucide-react";
|
import { FileIcon } from "lucide-react";
|
||||||
import { listSnapshotFilesOptions, restoreSnapshotMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { FileTree, type FileEntry } from "~/client/components/file-tree";
|
||||||
import { FileTree, type FileEntry } from "~/components/file-tree";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Checkbox } from "~/client/components/ui/checkbox";
|
||||||
import { Checkbox } from "~/components/ui/checkbox";
|
import { Label } from "~/client/components/ui/label";
|
||||||
import { Label } from "~/components/ui/label";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -16,10 +15,11 @@ import {
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/client/components/ui/alert-dialog";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip";
|
||||||
import type { Snapshot, Volume } from "~/lib/types";
|
import type { Snapshot, Volume } from "~/client/lib/types";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { listSnapshotFilesOptions, restoreSnapshotMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
snapshot: Snapshot;
|
snapshot: Snapshot;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { ListSnapshotsResponse } from "~/api-client/types.gen";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { cn } from "~/lib/utils";
|
import { Card } from "~/client/components/ui/card";
|
||||||
import { Card } from "~/components/ui/card";
|
import { ByteSize } from "~/client/components/bytes-size";
|
||||||
import { ByteSize } from "~/components/bytes-size";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import type { ListSnapshotsResponse } from "~/client/api-client";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
snapshots: ListSnapshotsResponse;
|
snapshots: ListSnapshotsResponse;
|
||||||
@@ -56,7 +56,7 @@ export const SnapshotTimeline = (props: Props) => {
|
|||||||
<div className="w-full bg-card">
|
<div className="w-full bg-card">
|
||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center">
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<div className="flex gap-4 overflow-x-auto pb-2 [&>:first-child]:ml-2 [&>:last-child]:mr-2">
|
<div className="flex gap-4 overflow-x-auto pb-2 *:first:ml-2 *:last:mr-2">
|
||||||
{snapshots.map((snapshot, index) => {
|
{snapshots.map((snapshot, index) => {
|
||||||
const date = new Date(snapshot.time);
|
const date = new Date(snapshot.time);
|
||||||
const isSelected = snapshotId === snapshot.short_id;
|
const isSelected = snapshotId === snapshot.short_id;
|
||||||
@@ -2,7 +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 { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import {
|
import {
|
||||||
getBackupScheduleOptions,
|
getBackupScheduleOptions,
|
||||||
runBackupNowMutation,
|
runBackupNowMutation,
|
||||||
@@ -10,15 +10,15 @@ import {
|
|||||||
listSnapshotsOptions,
|
listSnapshotsOptions,
|
||||||
updateBackupScheduleMutation,
|
updateBackupScheduleMutation,
|
||||||
stopBackupMutation,
|
stopBackupMutation,
|
||||||
} from "~/api-client/@tanstack/react-query.gen";
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { parseError } from "~/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { getCronExpression } from "~/utils/utils";
|
import { getCronExpression } from "~/utils/utils";
|
||||||
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
|
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
|
||||||
import { ScheduleSummary } from "../components/schedule-summary";
|
import { ScheduleSummary } from "../components/schedule-summary";
|
||||||
import { getBackupSchedule } from "~/api-client";
|
|
||||||
import type { Route } from "./+types/backup-details";
|
import type { Route } from "./+types/backup-details";
|
||||||
import { SnapshotFileBrowser } from "../components/snapshot-file-browser";
|
import { SnapshotFileBrowser } from "../components/snapshot-file-browser";
|
||||||
import { SnapshotTimeline } from "../components/snapshot-timeline";
|
import { SnapshotTimeline } from "../components/snapshot-timeline";
|
||||||
|
import { getBackupSchedule } from "~/client/api-client";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { CalendarClock, Database, HardDrive, Plus } from "lucide-react";
|
import { CalendarClock, Database, HardDrive, Plus } from "lucide-react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { listBackupSchedules } from "~/api-client";
|
|
||||||
import { listBackupSchedulesOptions } from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import { BackupStatusDot } from "../components/backup-status-dot";
|
import { BackupStatusDot } from "../components/backup-status-dot";
|
||||||
import { EmptyState } from "~/components/empty-state";
|
import { EmptyState } from "~/client/components/empty-state";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import type { Route } from "./+types/backups";
|
import type { Route } from "./+types/backups";
|
||||||
|
import { listBackupSchedules } from "~/client/api-client";
|
||||||
|
import { listBackupSchedulesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -68,7 +68,7 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
|
|||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
<HardDrive className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
<HardDrive className="h-5 w-5 text-muted-foreground shrink-0" />
|
||||||
<CardTitle className="text-lg truncate">
|
<CardTitle className="text-lg truncate">
|
||||||
Volume <span className="text-strong-accent">{schedule.volume.name}</span>
|
Volume <span className="text-strong-accent">{schedule.volume.name}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useId, useState } from "react";
|
import { useId, useState } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { Database, HardDrive } from "lucide-react";
|
import { Database, HardDrive } from "lucide-react";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link, useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -7,16 +7,16 @@ import {
|
|||||||
createBackupScheduleMutation,
|
createBackupScheduleMutation,
|
||||||
listRepositoriesOptions,
|
listRepositoriesOptions,
|
||||||
listVolumesOptions,
|
listVolumesOptions,
|
||||||
} from "~/api-client/@tanstack/react-query.gen";
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card, CardContent } from "~/components/ui/card";
|
import { Card, CardContent } from "~/client/components/ui/card";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
import { parseError } from "~/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { EmptyState } from "~/components/empty-state";
|
import { EmptyState } from "~/client/components/empty-state";
|
||||||
import { getCronExpression } from "~/utils/utils";
|
import { getCronExpression } from "~/utils/utils";
|
||||||
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
|
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
|
||||||
import type { Route } from "./+types/create-backup";
|
import type { Route } from "./+types/create-backup";
|
||||||
import { listRepositories, listVolumes } from "~/api-client";
|
import { listRepositories, listVolumes } from "~/client/api-client";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -168,7 +168,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
|
|||||||
<div className="absolute inset-0 animate-pulse">
|
<div className="absolute inset-0 animate-pulse">
|
||||||
<div className="w-24 h-24 rounded-full bg-primary/10 blur-2xl" />
|
<div className="w-24 h-24 rounded-full bg-primary/10 blur-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex items-center justify-center w-24 h-24 rounded-full bg-gradient-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
<div className="relative flex items-center justify-center w-24 h-24 rounded-full bg-linear-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
||||||
<Database className="w-12 h-12 text-primary/70" strokeWidth={1.5} />
|
<Database className="w-12 h-12 text-primary/70" strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2,9 +2,9 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { RotateCcw } from "lucide-react";
|
import { RotateCcw } from "lucide-react";
|
||||||
import { useId, useState } from "react";
|
import { useId, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { restoreSnapshotMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { restoreSnapshotMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { parseError } from "~/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/client/components/ui/dialog";
|
||||||
import { ScrollArea } from "~/components/ui/scroll-area";
|
import { ScrollArea } from "~/client/components/ui/scroll-area";
|
||||||
import { RestoreSnapshotForm, type RestoreSnapshotFormValues } from "./restore-snapshot-form";
|
import { RestoreSnapshotForm, type RestoreSnapshotFormValues } from "./restore-snapshot-form";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
import {
|
||||||
import { Input } from "~/components/ui/input";
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "~/client/components/ui/form";
|
||||||
|
import { Input } from "~/client/components/ui/input";
|
||||||
|
|
||||||
const restoreSnapshotFormSchema = type({
|
const restoreSnapshotFormSchema = type({
|
||||||
path: "string?",
|
path: "string?",
|
||||||
@@ -2,18 +2,18 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { Database, RotateCcw } from "lucide-react";
|
import { Database, RotateCcw } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { listRepositories } from "~/api-client/sdk.gen";
|
import { listRepositories } from "~/client/api-client/sdk.gen";
|
||||||
import { listRepositoriesOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { CreateRepositoryDialog } from "~/components/create-repository-dialog";
|
import { CreateRepositoryDialog } from "~/client/components/create-repository-dialog";
|
||||||
import { RepositoryIcon } from "~/components/repository-icon";
|
import { RepositoryIcon } from "~/client/components/repository-icon";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card } from "~/components/ui/card";
|
import { Card } from "~/client/components/ui/card";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
|
||||||
import type { Route } from "./+types/repositories";
|
import type { Route } from "./+types/repositories";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { EmptyState } from "~/components/empty-state";
|
import { EmptyState } from "~/client/components/empty-state";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -79,13 +79,13 @@ export default function Repositories({ loaderData }: Route.ComponentProps) {
|
|||||||
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 md:justify-between p-4 bg-card-header py-4">
|
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 md:justify-between p-4 bg-card-header py-4">
|
||||||
<span className="flex flex-col sm:flex-row items-stretch md:items-center gap-0 flex-wrap ">
|
<span className="flex flex-col sm:flex-row items-stretch md:items-center gap-0 flex-wrap ">
|
||||||
<Input
|
<Input
|
||||||
className="w-full lg:w-[180px] min-w-[180px] mr-[-1px] mt-[-1px]"
|
className="w-full lg:w-[180px] min-w-[180px] -mr-px -mt-px"
|
||||||
placeholder="Search repositories…"
|
placeholder="Search repositories…"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||||
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] mr-[-1px] mt-[-1px]">
|
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] -mr-px -mt-px">
|
||||||
<SelectValue placeholder="All status" />
|
<SelectValue placeholder="All status" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -95,7 +95,7 @@ export default function Repositories({ loaderData }: Route.ComponentProps) {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select value={backendFilter} onValueChange={setBackendFilter}>
|
<Select value={backendFilter} onValueChange={setBackendFilter}>
|
||||||
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] mt-[-1px]">
|
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] -mt-px">
|
||||||
<SelectValue placeholder="All backends" />
|
<SelectValue placeholder="All backends" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
doctorRepositoryMutation,
|
doctorRepositoryMutation,
|
||||||
getRepositoryOptions,
|
getRepositoryOptions,
|
||||||
listSnapshotsOptions,
|
listSnapshotsOptions,
|
||||||
} from "~/api-client/@tanstack/react-query.gen";
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -17,12 +17,12 @@ import {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/client/components/ui/alert-dialog";
|
||||||
import { parseError } from "~/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { getRepository } from "~/api-client/sdk.gen";
|
import { getRepository } from "~/client/api-client/sdk.gen";
|
||||||
import type { Route } from "./+types/repository-details";
|
import type { Route } from "./+types/repository-details";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/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 } from "lucide-react";
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { redirect, useParams } from "react-router";
|
import { redirect, useParams } from "react-router";
|
||||||
import { listSnapshotFilesOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { listSnapshotFilesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { RestoreSnapshotDialog } from "../components/restore-snapshot-dialog";
|
import { RestoreSnapshotDialog } from "../components/restore-snapshot-dialog";
|
||||||
import { SnapshotFileBrowser } from "~/modules/backups/components/snapshot-file-browser";
|
import { SnapshotFileBrowser } from "~/client/modules/backups/components/snapshot-file-browser";
|
||||||
import { getSnapshotDetails } from "~/api-client";
|
import { getSnapshotDetails } from "~/client/api-client";
|
||||||
import type { Route } from "./+types/snapshot-details";
|
import type { Route } from "./+types/snapshot-details";
|
||||||
|
|
||||||
export function meta({ params }: Route.MetaArgs) {
|
export function meta({ params }: Route.MetaArgs) {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card } from "~/components/ui/card";
|
import { Card } from "~/client/components/ui/card";
|
||||||
import type { Repository } from "~/lib/types";
|
import type { Repository } from "~/client/lib/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Database } from "lucide-react";
|
import { Database } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { listSnapshotsOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { listSnapshotsOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { SnapshotsTable } from "~/components/snapshots-table";
|
import { SnapshotsTable } from "~/client/components/snapshots-table";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Table, TableBody, TableCell, TableRow } from "~/components/ui/table";
|
import { Table, TableBody, TableCell, TableRow } from "~/client/components/ui/table";
|
||||||
import type { Repository, Snapshot } from "~/lib/types";
|
import type { Repository, Snapshot } from "~/client/lib/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
@@ -83,7 +83,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
|
|||||||
<div className="absolute inset-0 animate-pulse">
|
<div className="absolute inset-0 animate-pulse">
|
||||||
<div className="w-32 h-32 rounded-full bg-primary/10 blur-2xl" />
|
<div className="w-32 h-32 rounded-full bg-primary/10 blur-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex items-center justify-center w-32 h-32 rounded-full bg-gradient-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
<div className="relative flex items-center justify-center w-32 h-32 rounded-full bg-linear-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
||||||
<Database className="w-16 h-16 text-primary/70" strokeWidth={1.5} />
|
<Database className="w-16 h-16 text-primary/70" strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +110,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Input
|
<Input
|
||||||
className="w-full lg:w-[240px]"
|
className="w-full lg:w-60"
|
||||||
placeholder="Search snapshots..."
|
placeholder="Search snapshots..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
@@ -3,13 +3,8 @@ import { Download, KeyRound, User } 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";
|
||||||
import {
|
import { Button } from "~/client/components/ui/button";
|
||||||
changePasswordMutation,
|
import { Card, CardContent, CardDescription, CardTitle } from "~/client/components/ui/card";
|
||||||
downloadResticPasswordMutation,
|
|
||||||
logoutMutation,
|
|
||||||
} from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { Card, CardContent, CardDescription, CardTitle } from "~/components/ui/card";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -18,11 +13,16 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/client/components/ui/dialog";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/client/components/ui/label";
|
||||||
import { appContext } from "~/context";
|
import { appContext } from "~/context";
|
||||||
import type { Route } from "./+types/settings";
|
import type { Route } from "./+types/settings";
|
||||||
|
import {
|
||||||
|
changePasswordMutation,
|
||||||
|
downloadResticPasswordMutation,
|
||||||
|
logoutMutation,
|
||||||
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -2,11 +2,11 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { HeartIcon } from "lucide-react";
|
import { HeartIcon } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { healthCheckVolumeMutation, updateVolumeMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { healthCheckVolumeMutation, updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { OnOff } from "~/components/onoff";
|
import { OnOff } from "~/client/components/onoff";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import type { Volume } from "~/lib/types";
|
import type { Volume } from "~/client/lib/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
volume: Volume;
|
volume: Volume;
|
||||||
@@ -54,7 +54,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col flex-1 justify-start">
|
<div className="flex flex-col flex-1 justify-start">
|
||||||
{volume.lastError && <span className="text-sm text-red-500 break-words">{volume.lastError}</span>}
|
{volume.lastError && <span className="text-sm text-red-500 wrap-break-word">{volume.lastError}</span>}
|
||||||
{volume.status === "mounted" && <span className="text-md text-emerald-500">Healthy</span>}
|
{volume.status === "mounted" && <span className="text-md text-emerald-500">Healthy</span>}
|
||||||
{volume.status !== "unmounted" && (
|
{volume.status !== "unmounted" && (
|
||||||
<span className="text-xs text-muted-foreground mb-4">Checked {timeAgo || "never"}</span>
|
<span className="text-xs text-muted-foreground mb-4">Checked {timeAgo || "never"}</span>
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
import { HardDrive, Unplug } from "lucide-react";
|
import { HardDrive, Unplug } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Label, Pie, PieChart } from "recharts";
|
import { Label, Pie, PieChart } from "recharts";
|
||||||
import { ByteSize } from "~/components/bytes-size";
|
import { ByteSize } from "~/client/components/bytes-size";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "~/components/ui/chart";
|
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "~/client/components/ui/chart";
|
||||||
import type { StatFs } from "~/lib/types";
|
import type { StatFs } from "~/client/lib/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
statfs: StatFs;
|
statfs: StatFs;
|
||||||
@@ -2,15 +2,9 @@ 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 {
|
import { StatusDot } from "~/client/components/status-dot";
|
||||||
deleteVolumeMutation,
|
import { Button } from "~/client/components/ui/button";
|
||||||
getVolumeOptions,
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
|
||||||
mountVolumeMutation,
|
|
||||||
unmountVolumeMutation,
|
|
||||||
} from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import { StatusDot } from "~/components/status-dot";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -19,17 +13,23 @@ import {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/client/components/ui/alert-dialog";
|
||||||
import { VolumeIcon } from "~/components/volume-icon";
|
import { VolumeIcon } from "~/client/components/volume-icon";
|
||||||
import { parseError } from "~/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import type { Route } from "./+types/volume-details";
|
import type { Route } from "./+types/volume-details";
|
||||||
import { getVolume } from "~/api-client";
|
|
||||||
import { VolumeInfoTabContent } from "../tabs/info";
|
import { VolumeInfoTabContent } from "../tabs/info";
|
||||||
import { FilesTabContent } from "../tabs/files";
|
import { FilesTabContent } from "../tabs/files";
|
||||||
import { DockerTabContent } from "../tabs/docker";
|
import { DockerTabContent } from "../tabs/docker";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip";
|
||||||
import { useSystemInfo } from "~/hooks/use-system-info";
|
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||||
|
import { getVolume } from "~/client/api-client";
|
||||||
|
import {
|
||||||
|
deleteVolumeMutation,
|
||||||
|
getVolumeOptions,
|
||||||
|
mountVolumeMutation,
|
||||||
|
unmountVolumeMutation,
|
||||||
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export function meta({ params }: Route.MetaArgs) {
|
export function meta({ params }: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -42,7 +42,7 @@ export function meta({ params }: Route.MetaArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
|
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
|
||||||
const volume = await getVolume({ path: { name: params.name ?? "" } });
|
const volume = await getVolume({ path: { name: params.name } });
|
||||||
if (volume.data) return volume.data;
|
if (volume.data) return volume.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2,18 +2,18 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { HardDrive, RotateCcw } from "lucide-react";
|
import { HardDrive, RotateCcw } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { listVolumes } from "~/api-client";
|
import { CreateVolumeDialog } from "~/client/components/create-volume-dialog";
|
||||||
import { listVolumesOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { EmptyState } from "~/client/components/empty-state";
|
||||||
import { CreateVolumeDialog } from "~/components/create-volume-dialog";
|
import { StatusDot } from "~/client/components/status-dot";
|
||||||
import { EmptyState } from "~/components/empty-state";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { StatusDot } from "~/components/status-dot";
|
import { Card } from "~/client/components/ui/card";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Card } from "~/components/ui/card";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
import { VolumeIcon } from "~/client/components/volume-icon";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
|
||||||
import { VolumeIcon } from "~/components/volume-icon";
|
|
||||||
import type { Route } from "./+types/volumes";
|
import type { Route } from "./+types/volumes";
|
||||||
|
import { listVolumes } from "~/client/api-client";
|
||||||
|
import { listVolumesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -79,13 +79,13 @@ export default function Volumes({ loaderData }: Route.ComponentProps) {
|
|||||||
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 md:justify-between p-4 bg-card-header py-4">
|
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 md:justify-between p-4 bg-card-header py-4">
|
||||||
<span className="flex flex-col sm:flex-row items-stretch md:items-center gap-0 flex-wrap ">
|
<span className="flex flex-col sm:flex-row items-stretch md:items-center gap-0 flex-wrap ">
|
||||||
<Input
|
<Input
|
||||||
className="w-full lg:w-[180px] min-w-[180px] mr-[-1px] mt-[-1px]"
|
className="w-full lg:w-[180px] min-w-[180px] -mr-px -mt-px"
|
||||||
placeholder="Search volumes…"
|
placeholder="Search volumes…"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||||
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] mr-[-1px] mt-[-1px]">
|
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] -mr-px -mt-px">
|
||||||
<SelectValue placeholder="All status" />
|
<SelectValue placeholder="All status" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -95,7 +95,7 @@ export default function Volumes({ loaderData }: Route.ComponentProps) {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select value={backendFilter} onValueChange={setBackendFilter}>
|
<Select value={backendFilter} onValueChange={setBackendFilter}>
|
||||||
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] mt-[-1px]">
|
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] -mt-px">
|
||||||
<SelectValue placeholder="All backends" />
|
<SelectValue placeholder="All backends" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Unplug } from "lucide-react";
|
import { Unplug } from "lucide-react";
|
||||||
import * as YML from "yaml";
|
import * as YML from "yaml";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { getContainersUsingVolumeOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { CodeBlock } from "~/components/ui/code-block";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
import { CodeBlock } from "~/client/components/ui/code-block";
|
||||||
import type { Volume } from "~/lib/types";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
|
||||||
import { getContainersUsingVolumeOptions } from "../../../api-client/@tanstack/react-query.gen";
|
import type { Volume } from "~/client/lib/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
volume: Volume;
|
volume: Volume;
|
||||||
@@ -52,7 +52,7 @@ export const DockerTabContent = ({ volume }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 xl:grid-cols-[minmax(0,_1fr)_minmax(0,_1fr)]">
|
<div className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Plug-and-play Docker integration</CardTitle>
|
<CardTitle>Plug-and-play Docker integration</CardTitle>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FolderOpen } from "lucide-react";
|
import { FolderOpen } from "lucide-react";
|
||||||
import { VolumeFileBrowser } from "~/components/volume-file-browser";
|
import { VolumeFileBrowser } from "~/client/components/volume-file-browser";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import type { Volume } from "~/lib/types";
|
import type { Volume } from "~/client/lib/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
volume: Volume;
|
volume: Volume;
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateVolumeMutation } from "~/api-client/@tanstack/react-query.gen";
|
import { CreateVolumeForm, type FormValues } from "~/client/components/create-volume-form";
|
||||||
import { CreateVolumeForm, type FormValues } from "~/components/create-volume-form";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -12,11 +11,12 @@ import {
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/client/components/ui/alert-dialog";
|
||||||
import { Card } from "~/components/ui/card";
|
import { Card } from "~/client/components/ui/card";
|
||||||
import type { StatFs, Volume } from "~/lib/types";
|
import type { StatFs, Volume } from "~/client/lib/types";
|
||||||
import { HealthchecksCard } from "../components/healthchecks-card";
|
import { HealthchecksCard } from "../components/healthchecks-card";
|
||||||
import { StorageChart } from "../components/storage-chart";
|
import { StorageChart } from "../components/storage-chart";
|
||||||
|
import { updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
volume: Volume;
|
volume: Volume;
|
||||||
@@ -57,7 +57,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid gap-4 xl:grid-cols-[minmax(0,_2.3fr)_minmax(320px,_1fr)]">
|
<div className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<CreateVolumeForm
|
<CreateVolumeForm
|
||||||
initialValues={{ ...volume, ...volume.config }}
|
initialValues={{ ...volume, ...volume.config }}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user