feat: sdk migration in progress

This commit is contained in:
allanice001
2025-11-02 13:19:30 +00:00
commit 0d10d42442
492 changed files with 71067 additions and 0 deletions

40
ui/src/api/me.ts Normal file
View File

@@ -0,0 +1,40 @@
import { withRefresh } from "@/api/with-refresh.ts"
import type {
HandlersCreateUserKeyRequest,
HandlersMeResponse,
HandlersUpdateMeRequest,
HandlersUserAPIKeyOut,
ModelsUser,
} from "@/sdk"
import { makeMeApi, makeMeKeysApi } from "@/sdkClient.ts"
const me = makeMeApi()
const keys = makeMeKeysApi()
export const meApi = {
getMe: () =>
withRefresh(async (): Promise<HandlersMeResponse> => {
return await me.getMe()
}),
updateMe: (body: HandlersUpdateMeRequest) =>
withRefresh(async (): Promise<ModelsUser> => {
return await me.updateMe({ body })
}),
listKeys: () =>
withRefresh(async (): Promise<HandlersUserAPIKeyOut[]> => {
return await keys.listUserAPIKeys()
}),
createKey: (body: HandlersCreateUserKeyRequest) =>
withRefresh(async (): Promise<HandlersUserAPIKeyOut> => {
return await keys.createUserAPIKey({ body })
}),
deleteKey: (id: string) =>
withRefresh(async (): Promise<boolean> => {
await keys.deleteUserAPIKey({ id })
return true
}),
}

28
ui/src/api/servers.ts Normal file
View File

@@ -0,0 +1,28 @@
import { withRefresh } from "@/api/with-refresh.ts"
import type { DtoCreateServerRequest, DtoUpdateServerRequest } from "@/sdk"
import { makeServersApi } from "@/sdkClient.ts"
const servers = makeServersApi()
export const serversApi = {
listServers: () =>
withRefresh(async () => {
return await servers.listServers()
}),
createServer: (body: DtoCreateServerRequest) =>
withRefresh(async () => {
return await servers.createServer({ body })
}),
getServer: (id: string) =>
withRefresh(async () => {
return await servers.getServer({ id })
}),
updateServer: (id: string, body: DtoUpdateServerRequest) =>
withRefresh(async () => {
return await servers.updateServer({ id, body })
}),
deleteServer: (id: string) =>
withRefresh(async () => {
await servers.deleteServer({ id })
}),
}

75
ui/src/api/ssh.ts Normal file
View File

@@ -0,0 +1,75 @@
import { withRefresh } from "@/api/with-refresh.ts"
import type { DtoCreateSSHRequest, DtoSshResponse, DtoSshRevealResponse } from "@/sdk"
import { makeSshApi } from "@/sdkClient.ts"
const ssh = makeSshApi()
export type SshDownloadPart = "public" | "private" | "both"
export const sshApi = {
listSshKeys: () =>
withRefresh(async (): Promise<DtoSshResponse[]> => {
return await ssh.listPublicSshKeys()
}),
createSshKey: (body: DtoCreateSSHRequest) =>
withRefresh(async (): Promise<DtoSshResponse> => {
// SDK expects { body }
return await ssh.createSSHKey({ body })
}),
getSshKeyById: (id: string) =>
withRefresh(async (): Promise<DtoSshResponse> => {
return await ssh.getSSHKey({ id })
}),
revealSshKeyById: (id: string) =>
withRefresh(async (): Promise<DtoSshRevealResponse> => {
return await ssh.getSSHKey({ id, reveal: true as any })
// Note: TS fetch generator often models query params as part of params bag.
// If your generated client uses a different shape, change to:
// return await ssh.getSSHKeyRaw({ id, reveal: true }).then(r => r.value())
}),
deleteSshKey: (id: string) =>
withRefresh(async (): Promise<void> => {
await ssh.deleteSSHKey({ id })
}),
// 1) JSON mode: returns structured JSON with filenames & (optionally) base64 zip
downloadJson: (id: string, part: SshDownloadPart) =>
withRefresh(async () => {
const url = new URL(`/api/v1/ssh/${id}/download`, window.location.origin)
url.searchParams.set("part", part)
url.searchParams.set("mode", "json")
const res = await fetch(url.toString())
if (!res.ok) throw new Error(`Download failed: ${res.statusText}`)
return (await res.json()) as {
id: string
name: string | null
fingerprint: string
filenames: string[]
publicKey?: string | null
privatePEM?: string | null
zipBase64?: string | null
}
}),
// 2) Attachment mode: returns a Blob (public/private file or a .zip)
downloadBlob: (id: string, part: SshDownloadPart) =>
withRefresh(async (): Promise<{ filename: string; blob: Blob }> => {
const url = new URL(`/api/v1/ssh/${id}/download`, window.location.origin)
url.searchParams.set("part", part)
const res = await fetch(url.toString())
if (!res.ok) throw new Error(`Download failed: ${res.statusText}`)
// Parse filename from Content-Disposition
const cd = res.headers.get("Content-Disposition") || ""
const match = /filename="([^"]+)"/i.exec(cd)
const filename = match?.[1] ?? "ssh-key-download"
const blob = await res.blob()
return { filename, blob }
}),
}

23
ui/src/api/taints.ts Normal file
View File

@@ -0,0 +1,23 @@
import { withRefresh } from "@/api/with-refresh.ts"
import type { DtoCreateTaintRequest, DtoUpdateTaintRequest } from "@/sdk"
import { makeTaintsApi } from "@/sdkClient.ts"
const taints = makeTaintsApi()
export const taintsApi = {
listTaints: () =>
withRefresh(async () => {
return await taints.listTaints()
}),
createTaint: (body: DtoCreateTaintRequest) =>
withRefresh(async () => {
return await taints.createTaint({ body })
}),
deleteTaint: (id: string) =>
withRefresh(async () => {
await taints.deleteTaint({ id })
}),
updateTaint: (id: string, body: DtoUpdateTaintRequest) =>
withRefresh(async () => {
return await taints.updateTaint({ id, body })
}),
}

View File

@@ -0,0 +1,62 @@
// api/with-refresh.ts
import { authStore, type TokenPair } from "@/auth/store.ts"
import { API_BASE } from "@/sdkClient.ts"
let inflightRefresh: Promise<boolean> | null = null
async function doRefresh(): Promise<boolean> {
const tokens = authStore.get()
if (!tokens?.refresh_token) return false
try {
const res = await fetch(`${API_BASE}/auth/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: tokens.refresh_token }),
})
if (!res.ok) return false
const next = (await res.json()) as TokenPair
authStore.set(next)
return true
} catch {
return false
}
}
async function refreshOnce(): Promise<boolean> {
if (!inflightRefresh) {
inflightRefresh = doRefresh().finally(() => {
inflightRefresh = null
})
}
return inflightRefresh
}
function isUnauthorized(err: any): boolean {
return (
err?.status === 401 ||
err?.cause?.status === 401 ||
err?.response?.status === 401 ||
(err instanceof Response && err.status === 401)
)
}
export async function withRefresh<T>(fn: () => Promise<T>): Promise<T> {
// Optional: attempt a proactive refresh if close to expiry
if (authStore.willExpireSoon?.(30)) {
await refreshOnce()
}
try {
return await fn()
} catch (error) {
if (!isUnauthorized(error)) throw error
const ok = await refreshOnce()
if (!ok) throw error
return await fn()
}
}