feat: node info, build info and acrylic web theme
Squash of recent dev work: return_session login, backend switcher, line detail timeline, overview strips, node platform/country/ISP info with flags and icons, build info (version, tag, time) behind site.show_build, and the acrylic display theme.
This commit is contained in:
1 parent
10634bdd80
commit
14b0de3544
95 files changed
+5524
-528
No files matched your search
@@ -41,17 +41,65 @@ export function getBase(): string {
|
||||
return normalizeBase(read(BASE_KEY) || (import.meta.env.VITE_API_BASE as string | undefined) || '')
|
||||
}
|
||||
|
||||
export interface SavedSource {
|
||||
base: string
|
||||
/** 在这个后端登录之后拿到的会话(只存在浏览器里);空 = 还没登录。 */
|
||||
token: string
|
||||
}
|
||||
|
||||
// v2:以前保存的是手填的管理 Token,现在是登录后拿到的会话,旧的不再使用
|
||||
const listKey = () => `monitor.sources.v2.${app}`
|
||||
|
||||
function readList(): SavedSource[] {
|
||||
try {
|
||||
const parsed = JSON.parse(read(listKey()) || '[]') as unknown
|
||||
return Array.isArray(parsed) ? (parsed as SavedSource[]).filter((x) => typeof x?.base === 'string' && x.base !== '') : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存过的后端(每个前端各存各的登录会话);当前连接的那个一定在里面。不含这个页面自己所在的后端。 */
|
||||
export function listSources(): SavedSource[] {
|
||||
const list = readList()
|
||||
const active = getBase()
|
||||
if (active && !list.some((x) => x.base === active)) list.push({ base: active, token: '' })
|
||||
return list
|
||||
}
|
||||
|
||||
function writeList(list: SavedSource[]) {
|
||||
write(listKey(), JSON.stringify(list))
|
||||
}
|
||||
|
||||
/** 当前连接的后端的登录会话;这个页面自己所在的后端用 Cookie,没有。 */
|
||||
export function getToken(): string {
|
||||
return read(`monitor.api_token.${app}`)
|
||||
const active = getBase()
|
||||
return active ? (readList().find((x) => x.base === active)?.token ?? '') : ''
|
||||
}
|
||||
|
||||
/** 保存(或更新)一个后端和它的登录会话,并连过去。地址留空 = 这个页面自己所在的后端。 */
|
||||
export function setSource(base: string, token: string) {
|
||||
write(BASE_KEY, normalizeBase(base))
|
||||
write(`monitor.api_token.${app}`, token.trim())
|
||||
const next = normalizeBase(base)
|
||||
if (next) writeList([...readList().filter((x) => x.base !== next), { base: next, token }])
|
||||
write(BASE_KEY, next)
|
||||
}
|
||||
|
||||
/** 切换到已经保存过的后端(空串 = 这个页面自己所在的后端)。 */
|
||||
export function activateSource(base: string) {
|
||||
write(BASE_KEY, normalizeBase(base))
|
||||
}
|
||||
|
||||
/** 忘掉一个保存过的后端;正连着的话回到这个页面自己所在的后端。 */
|
||||
export function removeSource(base: string) {
|
||||
writeList(readList().filter((x) => x.base !== base))
|
||||
if (getBase() === base) write(BASE_KEY, '')
|
||||
}
|
||||
|
||||
/** 只改当前连接的后端的登录会话(过期、退出时清掉)。 */
|
||||
export function setToken(token: string) {
|
||||
write(`monitor.api_token.${app}`, token.trim())
|
||||
const active = getBase()
|
||||
if (!active) return
|
||||
writeList([...readList().filter((x) => x.base !== active), { base: active, token }])
|
||||
}
|
||||
|
||||
export const isRemote = () => getBase() !== ''
|
||||
@@ -66,6 +114,18 @@ export function authHeaders(): Record<string, string> {
|
||||
return isRemote() && token ? { Authorization: `Bearer ${token}` } : {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前后端连不连得上:只看有没有响应(跨域时拿不到内容也没关系,所以用 no-cors);网络不通、域名解析失败、连接被拒才算失败。
|
||||
*/
|
||||
export async function pingBackend(): Promise<boolean> {
|
||||
try {
|
||||
await fetch(resolveUrl('/healthz'), { mode: 'no-cors', cache: 'no-store' })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export interface LiveSource {
|
||||
onopen: (() => void) | null
|
||||
onerror: (() => void) | null
|
||||
|
||||
Reference in new issue
Block a user