import axios, { AxiosInstance } from "axios" export interface Pool { name: string size: number alloc: number free: number fragmentation: string capacity: string health: "ONLINE" | "DEGRADED" | "FAULTED" | "OFFLINE" | "UNAVAIL" } export interface Vdev { name: string state: string read: number write: number cksum: number children?: Vdev[] } export interface PoolStatus extends Pool { state?: string scan?: string errors?: string vdevs: Vdev[] } export interface Dataset { name: string type: "filesystem" | "volume" | "snapshot" used: number avail: number refer: number mountpoint?: string compression?: string quota?: number reservation?: number } export interface DatasetProperties { compression?: string quota?: number reservation?: number } export interface Snapshot { name: string dataset?: string created: number used: number referenced: number creation_datetime?: string } export interface SambaShare { name: string path: string comment?: string read_only?: boolean guest_ok?: boolean valid_users?: string } export interface NfsShare { path: string clients: string options?: string } export interface SystemInfo { hostname: string uptime: number memory_total: number memory_used: number memory_available: number cpu_count: number cpu_model: string os: string } export interface SystemStatus { status: string zfs_available: boolean version: string } export interface SystemUser { username: string uid: number gid: number home: string shell: string gecos?: string locked?: boolean groups: string[] } export interface SystemGroup { groupname: string gid: number members: string[] } export interface LoginEntry { user: string terminal: string host: string login_time: string logout_time?: string duration?: string } export class ZFSManagerAPI { private client: AxiosInstance private token: string | null = null constructor(baseURL: string = process.env.NEXT_PUBLIC_API_URL || "") { this.client = axios.create({ baseURL, headers: { "Content-Type": "application/json", }, }) if (typeof window !== "undefined") { this.token = localStorage.getItem("access_token") if (this.token) { this.setAuthHeader() } } this.client.interceptors.response.use( (response) => response, (error) => { // Don't redirect on login endpoint itself const isLoginEndpoint = error.config?.url?.includes("/api/auth/login") if (error.response?.status === 401 && !isLoginEndpoint) { localStorage.removeItem("access_token") this.token = null if (typeof window !== "undefined") { window.location.href = "/login" } } return Promise.reject(error) } ) } private setAuthHeader() { if (this.token) { this.client.defaults.headers.common["Authorization"] = `Bearer ${this.token}` } } // Auth async login(username: string, password: string): Promise<{ access_token: string; token_type: string }> { // Clear any existing token before login localStorage.removeItem("access_token") this.token = null this.client.defaults.headers.common["Authorization"] = "" try { const response = await this.client.post("/api/auth/login", { username, password }) this.token = response.data.access_token this.setAuthHeader() if (this.token) { localStorage.setItem("access_token", this.token) } return response.data } catch (error: any) { // Clear any invalid token on login failure localStorage.removeItem("access_token") this.token = null this.client.defaults.headers.common["Authorization"] = "" // Provide better error message const message = error?.response?.data?.detail || error?.message || "Login failed" const err = new Error(message) throw err } } async logout() { this.token = null this.client.defaults.headers.common["Authorization"] = "" localStorage.removeItem("access_token") } async verifyToken(): Promise<{ valid: boolean; username: string }> { try { const response = await this.client.post("/api/auth/verify") return response.data } catch { return { valid: false, username: "" } } } // System Status (no auth required) async getSystemStatus(): Promise { try { const response = await this.client.get("/api/status") return response.data } catch { return { status: "unknown", zfs_available: false, version: "" } } } // Pools async getPools(): Promise { const response = await this.client.get("/api/pools/") return response.data } async getPoolStatus(name: string): Promise { const response = await this.client.get(`/api/pools/${name}`) return response.data } async startScrub(poolName: string): Promise<{ status: string }> { const response = await this.client.post(`/api/pools/${poolName}/scrub`) return response.data } // Datasets async getDatasets(pool: string = "tank"): Promise { const response = await this.client.get("/api/datasets/", { params: { pool } }) return response.data } async createDataset(name: string, properties?: Record): Promise<{ status: string }> { const response = await this.client.post("/api/datasets/", { name, properties }) return response.data } async updateDatasetProperties(name: string, props: DatasetProperties): Promise<{ status: string }> { const response = await this.client.patch(`/api/datasets/${name}`, props) return response.data } async deleteDataset(name: string, recursive = false): Promise<{ status: string }> { const response = await this.client.delete(`/api/datasets/${name}`, { params: { recursive } }) return response.data } // Snapshots async getSnapshots(dataset?: string, limit = 50): Promise { const response = await this.client.get("/api/snapshots/", { params: { ...(dataset ? { dataset } : {}), limit }, }) return response.data } async createSnapshot(dataset: string, name?: string): Promise<{ status: string }> { const response = await this.client.post("/api/snapshots/", { dataset, name }) return response.data } async deleteSnapshot(name: string): Promise<{ status: string }> { const response = await this.client.delete(`/api/snapshots/${name}`) return response.data } async rollbackSnapshot(snapshot: string): Promise<{ status: string }> { const response = await this.client.post("/api/snapshots/rollback", { snapshot }) return response.data } // Shares — Samba async getSambaShares(): Promise { const response = await this.client.get("/api/shares/samba") return response.data.shares ?? response.data } async createSambaShare(share: SambaShare): Promise<{ status: string }> { const response = await this.client.post("/api/shares/samba", share) return response.data } async updateSambaShare(oldName: string, share: SambaShare): Promise<{ status: string }> { const response = await this.client.put(`/api/shares/samba/${oldName}`, share) return response.data } async deleteSambaShare(name: string): Promise<{ status: string }> { const response = await this.client.delete(`/api/shares/samba/${name}`) return response.data } async getSambaConfig(): Promise<{ parameters: Array<{ key: string; value: string }> }> { const response = await this.client.get("/api/shares/samba/config") return response.data } async setSambaConfig(parameters: { [key: string]: string }): Promise<{ status: string }> { const response = await this.client.put("/api/shares/samba/config", { parameters }) return response.data } // Shares — NFS async getNfsShares(): Promise { const response = await this.client.get("/api/shares/nfs") return response.data.shares ?? response.data } async createNfsShare(share: NfsShare): Promise<{ status: string }> { const response = await this.client.post("/api/shares/nfs", share) return response.data } async deleteNfsShare(path: string): Promise<{ status: string }> { const response = await this.client.delete("/api/shares/nfs", { params: { path } }) return response.data } // Shares Configuration async getSambaGlobalConfig(): Promise<{ [key: string]: any }> { const response = await this.client.get("/api/shares/samba/config") return response.data } async setSambaGlobalConfig(config: string): Promise<{ status: string }> { const response = await this.client.put("/api/shares/samba/config", { config }) return response.data } async getNfsGlobalConfig(): Promise<{ exports: string; path?: string }> { const response = await this.client.get("/api/shares/nfs/config") return response.data } async setNfsGlobalConfig(config: string): Promise<{ status: string }> { const response = await this.client.put("/api/shares/nfs/config", { config }) return response.data } // System async getSystemInfo(): Promise { const response = await this.client.get("/api/system/info") return response.data } async getMemory(): Promise<{ total: number; used: number; available: number; swap_total: number; swap_used: number }> { const response = await this.client.get("/api/system/memory") return response.data } async getCpuInfo(): Promise<{ count: number; percent?: number; load_average: number[] }> { const response = await this.client.get("/api/system/cpu") return response.data } async getUptime(): Promise<{ uptime_seconds: number; uptime_string: string }> { const response = await this.client.get("/api/system/uptime") return response.data } async getNetwork(): Promise<{ interfaces: any[] }> { const response = await this.client.get("/api/system/network") return response.data } async getNetworkTraffic(): Promise<{ interfaces: any[] }> { const response = await this.client.get("/api/system/network/traffic") return response.data } async getDiskIO(): Promise<{ disks: any[] }> { const response = await this.client.get("/api/system/diskio") return response.data } async getServices(): Promise<{ services: any[] }> { const response = await this.client.get("/api/system/services") return response.data } async getUnits(): Promise<{ services: any[] targets: any[] sockets: any[] timers: any[] paths: any[] }> { const response = await this.client.get("/api/system/units") return response.data } async getSystemLogs(limit: number = 20): Promise<{ logs: string[] }> { const response = await this.client.get(`/api/system/logs?limit=${limit}`) return response.data } async getHealth(): Promise<{ status: string; version: string }> { const response = await this.client.get("/health") return response.data } // Identities - Users async getUsers(): Promise { const response = await this.client.get("/api/identities/users") return response.data.users ?? [] } async createUser(username: string, home_dir?: string, shell?: string, gecos?: string): Promise<{ status: string }> { const response = await this.client.post("/api/identities/users", { username, home_dir, shell, gecos }) return response.data } async deleteUser(username: string, remove_home: boolean = true): Promise<{ status: string }> { const response = await this.client.delete(`/api/identities/users/${username}`, { params: { remove_home } }) return response.data } async changePassword(username: string, password: string): Promise<{ status: string }> { const response = await this.client.post(`/api/identities/users/${username}/password`, { password }) return response.data } async changeShell(username: string, shell: string): Promise<{ status: string }> { const response = await this.client.post(`/api/identities/users/${username}/shell`, { shell }) return response.data } async lockUser(username: string): Promise<{ status: string }> { const response = await this.client.post(`/api/identities/users/${username}/lock`) return response.data } async unlockUser(username: string): Promise<{ status: string }> { const response = await this.client.post(`/api/identities/users/${username}/unlock`) return response.data } async setSambaPassword(username: string, password: string): Promise<{ status: string }> { const response = await this.client.post(`/api/identities/users/${username}/samba-password`, { password }) return response.data } // Identities - Groups async getGroups(): Promise { const response = await this.client.get("/api/identities/groups") return response.data.groups ?? [] } async createGroup(groupname: string): Promise<{ status: string }> { const response = await this.client.post("/api/identities/groups", { groupname }) return response.data } async deleteGroup(groupname: string): Promise<{ status: string }> { const response = await this.client.delete(`/api/identities/groups/${groupname}`) return response.data } async addUserToGroup(username: string, groupname: string): Promise<{ status: string }> { const response = await this.client.post(`/api/identities/users/${username}/groups`, { groupname }) return response.data } async removeUserFromGroup(username: string, groupname: string): Promise<{ status: string }> { const response = await this.client.delete(`/api/identities/users/${username}/groups/${groupname}`) return response.data } // Identities - Samba Users async getSambaUsers(): Promise { const response = await this.client.get("/api/identities/samba-users") return response.data.users ?? [] } // Identities - Login History async getLoginHistory(limit: number = 50): Promise { const response = await this.client.get("/api/identities/login-history", { params: { limit } }) return response.data.logins ?? [] } // Navigator - Copy, Move, Search async copyFile(src: string, dst: string, overwrite: boolean = false): Promise<{ status: string }> { const response = await this.client.post("/api/navigator/copy", { src, dst, overwrite }) return response.data } async moveFile(src: string, dst: string, overwrite: boolean = false): Promise<{ status: string }> { const response = await this.client.post("/api/navigator/move", { src, dst, overwrite }) return response.data } async searchFiles(q: string, path: string = "", limit: number = 50): Promise { const response = await this.client.get("/api/navigator/search", { params: { q, path, limit } }) return response.data.results ?? [] } } export const api = new ZFSManagerAPI()