mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 15:33:45 +08:00
fab3ec9aeb
Add Galaxy web assets and client settings interfaces with their bundled vendor notices. Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: prabhaavp <prabhaav@msu.edu>
106 lines
4.9 KiB
JavaScript
106 lines
4.9 KiB
JavaScript
import { PollTimer, connectionError } from "./polling.js"
|
|
import { requestJson } from "./startup.js"
|
|
import { deleteRecords } from "./record-deletion.js"
|
|
export function validCrashReports(data) {
|
|
return data?.schemaVersion === 1 && Array.isArray(data.reports) && data.reports.length <= 200 &&
|
|
typeof data.scanIncomplete === "boolean" && typeof data.listLimited === "boolean" &&
|
|
data.reports.every(item => typeof item.id === "string" && item.id && typeof item.name === "string" && item.name &&
|
|
typeof item.size === "number" && Number.isFinite(item.size) && item.size >= 0 &&
|
|
typeof item.modifiedAt === "number" && Number.isFinite(item.modifiedAt))
|
|
}
|
|
|
|
// Local stored crash reports. State is cleared on navigation and sign-out.
|
|
export class CrashReportsFeed {
|
|
constructor({ publish, unauthorized = () => {}, fetcher = (...args) => fetch(...args), later = (fn, ms) => setTimeout(fn, ms), cancel = id => clearTimeout(id) }) {
|
|
Object.assign(this, { publish, unauthorized, fetcher })
|
|
Object.assign(this, { later, cancel })
|
|
this.poller = new PollTimer({ read: () => this.load(true), interval: 30000, later, cancel })
|
|
this.active = false
|
|
this.generation = 0
|
|
this.request = null
|
|
this.reports = []
|
|
}
|
|
|
|
clear() {
|
|
this.reports = []
|
|
this.deleting = false
|
|
this.publish({ deleting: false, storageBytes: 0, reportCount: 0, reports: [], scanIncomplete: false, listLimited: false, status: "idle", error: "", selected: null, preview: null, previewStatus: "idle" })
|
|
}
|
|
|
|
stop() {
|
|
this.poller.stop()
|
|
this.active = false
|
|
this.generation++
|
|
this.request?.abort()
|
|
this.request = null
|
|
this.clear()
|
|
}
|
|
|
|
start() {
|
|
this.stop()
|
|
this.active = true
|
|
this.poller.start()
|
|
return this.load()
|
|
}
|
|
|
|
async requestJson(url, generation, request) {
|
|
try {
|
|
const body = await requestJson(url, { fetcher: this.fetcher, later: this.later, cancel: this.cancel, request: { signal: request.signal } })
|
|
return this.active && generation === this.generation && !request.signal.aborted ? body : null
|
|
} catch (error) {
|
|
if (!this.active || generation !== this.generation) return null
|
|
if (error.status === 401 || ["access_unavailable", "setup_required"].includes(error.code)) { this.stop(); this.unauthorized(); return null }
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async load(background = false) {
|
|
if (!this.active || background && this.request) return
|
|
const generation = ++this.generation
|
|
this.request?.abort()
|
|
const request = new AbortController()
|
|
this.request = request
|
|
if (!background) this.publish({ status: "loading" })
|
|
try {
|
|
const data = await this.requestJson("./api/crash-reports", generation, request)
|
|
if (!data) return
|
|
if (!validCrashReports(data)) throw new Error("Invalid crash report list")
|
|
this.reports = data.reports
|
|
this.publish({ reports: data.reports, scanIncomplete: data.scanIncomplete, listLimited: data.listLimited, reportCount: data.reportCount, storageBytes: data.storageBytes, storageIncomplete: data.storageIncomplete, status: "ready", error: "" })
|
|
} catch (error) {
|
|
if (this.active && generation === this.generation) this.publish({ reports: [], scanIncomplete: false, listLimited: false, status: "unavailable", error: connectionError(error), selected: null, preview: null, previewStatus: "idle" })
|
|
} finally {
|
|
if (generation === this.generation) this.request = null
|
|
}
|
|
}
|
|
|
|
remove(id, confirm) {
|
|
return deleteRecords(this, "./api/crash-reports/delete", id == null ? { all: true, confirmed: true } : { id }, {
|
|
confirm, subject: "crash reports", valid: data => validCrashReports(data) &&
|
|
Number.isSafeInteger(data.storageBytes) && data.storageBytes >= 0 && Number.isSafeInteger(data.reportCount) && data.reportCount >= 0 && typeof data.storageIncomplete === "boolean",
|
|
accept: data => { this.reports = data.reports; this.publish({ ...data, status: "ready", error: "" }) },
|
|
})
|
|
}
|
|
|
|
async open(report) {
|
|
if (!this.active || !this.reports.some((item) => item.id === report?.id)) return
|
|
const generation = ++this.generation
|
|
this.request?.abort()
|
|
const request = new AbortController()
|
|
this.request = request
|
|
this.publish({ selected: report.id, preview: null, previewStatus: "loading", error: "" })
|
|
try {
|
|
const data = await this.requestJson(`./api/crash-reports/${encodeURIComponent(report.id)}`, generation, request)
|
|
if (!data) return
|
|
if (data.schemaVersion !== 1 || data.name !== report.name || typeof data.text !== "string" || typeof data.truncated !== "boolean") {
|
|
throw new Error("Invalid crash report")
|
|
}
|
|
this.publish({ selected: report.id, preview: data, previewStatus: "ready", error: "" })
|
|
} catch (error) {
|
|
if (this.active && generation === this.generation) this.publish({ selected: null, preview: null, previewStatus: "unavailable", error: connectionError(error) })
|
|
} finally {
|
|
if (generation === this.generation) this.request = null
|
|
}
|
|
}
|
|
}
|