mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-27 18:34:29 +00:00
Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com> Co-authored-by: xiaomiku01 <xiaomiku01@outlook.com>
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
import { t } from "@lingui/core/macro"
|
|
import PocketBase from "pocketbase"
|
|
import { basePath } from "@/components/router"
|
|
import { toast } from "@/components/ui/use-toast"
|
|
import { dynamicActivate, getLocale } from "@/lib/i18n"
|
|
import type { ChartTimes, UserSettings } from "@/types"
|
|
import { $alerts, $allSystemsById, $allSystemsByName, $userSettings } from "./stores"
|
|
import { chartTimeData, debounce } from "./utils"
|
|
|
|
/** PocketBase JS Client */
|
|
export const pb = new PocketBase(basePath)
|
|
|
|
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
|
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
|
|
|
const verifyAuth = () => {
|
|
pb.collection("users")
|
|
.authRefresh()
|
|
.catch(() => {
|
|
logOut()
|
|
toast({
|
|
title: t`Failed to authenticate`,
|
|
description: t`Please log in again`,
|
|
variant: "destructive",
|
|
})
|
|
})
|
|
}
|
|
|
|
const verifyAuthDebounced = debounce(verifyAuth, 100)
|
|
|
|
// verify the session whenever any API request returns a 4xx response (e.g. an
|
|
// expired JWT). The auth-refresh endpoint is excluded to avoid a loop, since
|
|
// it returns 401 itself when the token is no longer valid.
|
|
pb.afterSend = (response, data) => {
|
|
if (
|
|
(response.status === 401 || response.status === 403) &&
|
|
pb.authStore.token &&
|
|
!response.url.includes("auth-refresh")
|
|
) {
|
|
verifyAuthDebounced()
|
|
}
|
|
return data
|
|
}
|
|
|
|
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
|
export function logOut() {
|
|
$allSystemsByName.set({})
|
|
$allSystemsById.set({})
|
|
$alerts.set({})
|
|
$userSettings.set({} as UserSettings)
|
|
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
|
pb.authStore.clear()
|
|
pb.realtime.unsubscribe()
|
|
}
|
|
|
|
/** Save a partial update to user settings in database immediately */
|
|
export async function saveUserSettings(newSettings: Partial<UserSettings>) {
|
|
// get fresh copy of settings so concurrent changes aren't overwritten
|
|
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "id,settings" })
|
|
const updatedSettings = await pb.collection("user_settings").update(req.id, {
|
|
settings: {
|
|
...req.settings,
|
|
...newSettings,
|
|
},
|
|
})
|
|
$userSettings.set(updatedSettings.settings)
|
|
}
|
|
|
|
// keys queued by queueUserSettings, flushed together in a single request so that
|
|
// two debounced saves for different keys can't race each other's read-modify-write
|
|
// and silently drop one of the changes
|
|
let queuedSettings: Partial<UserSettings> = {}
|
|
|
|
const flushQueuedSettings = debounce(() => {
|
|
const toSave = queuedSettings
|
|
queuedSettings = {}
|
|
if (Object.keys(toSave).length === 0) {
|
|
return
|
|
}
|
|
saveUserSettings(toSave).catch(console.error)
|
|
}, 1000)
|
|
|
|
/** Queue a partial user settings update, merging with any other pending keys and saving them together after a debounce window */
|
|
export function queueUserSettings(newSettings: Partial<UserSettings>) {
|
|
queuedSettings = { ...queuedSettings, ...newSettings }
|
|
flushQueuedSettings()
|
|
}
|
|
|
|
/** Fetch or create user settings in database */
|
|
export async function updateUserSettings() {
|
|
try {
|
|
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
|
$userSettings.set(req.settings)
|
|
dynamicActivate(req.settings.lang || getLocale())
|
|
return
|
|
} catch (e) {
|
|
console.error("get settings", e)
|
|
}
|
|
// create user settings if error fetching existing
|
|
try {
|
|
const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record?.id })
|
|
$userSettings.set(createdSettings.settings)
|
|
dynamicActivate(createdSettings.settings.lang || getLocale())
|
|
} catch (e) {
|
|
console.error("create settings", e)
|
|
}
|
|
}
|
|
|
|
export function getPbTimestamp(timeString: ChartTimes, d?: Date, createdIsNumber?: boolean) {
|
|
d ||= chartTimeData[timeString].getOffset(new Date())
|
|
if (createdIsNumber) {
|
|
return d.getTime()
|
|
}
|
|
const year = d.getUTCFullYear()
|
|
const month = String(d.getUTCMonth() + 1).padStart(2, "0")
|
|
const day = String(d.getUTCDate()).padStart(2, "0")
|
|
const hours = String(d.getUTCHours()).padStart(2, "0")
|
|
const minutes = String(d.getUTCMinutes()).padStart(2, "0")
|
|
const seconds = String(d.getUTCSeconds()).padStart(2, "0")
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
}
|