@@ -0,0 +1,102 @@
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
const bootstrap = ref<any>(null);
|
||||
const releases = ref<any>(null);
|
||||
const sources = ref<any>(null);
|
||||
const notices = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
let loaded = false;
|
||||
|
||||
async function fetchJSON(path: string) {
|
||||
const res = await fetch(path);
|
||||
if (!res.ok) throw new Error(`${path} returned HTTP ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export function usePortalState() {
|
||||
const packages = computed(() => releases.value?.packages || bootstrap.value?.release?.packages || []);
|
||||
const categories = computed(() => sources.value?.categories || bootstrap.value?.sources?.categories || []);
|
||||
const latestNotice = computed(() => notices.value[0] || releases.value?.latest_notice || bootstrap.value?.release?.latest_notice || null);
|
||||
const sourceCount = computed(() => categories.value.reduce((total: number, cat: any) => total + (cat.subcategories?.length || 0), 0));
|
||||
const healthyCount = computed(() => categories.value.reduce((total: number, cat: any) => {
|
||||
return total + (cat.subcategories || []).filter((item: any) => sourceStatus(item) === "ok").length;
|
||||
}, 0));
|
||||
const availability = computed(() => sourceCount.value ? Math.round((healthyCount.value / sourceCount.value) * 100) : 0);
|
||||
const downloadUrl = computed(() => releases.value?.download_url || bootstrap.value?.release?.download_url || "/update-info.json");
|
||||
const appVersion = computed(() => releases.value?.app_version || bootstrap.value?.release?.app_version || latestNotice.value?.version || "未发布");
|
||||
const databaseStatus = computed(() => bootstrap.value?.health?.database?.activeProvider || bootstrap.value?.health?.database?.configProvider || "-");
|
||||
const serviceVersion = computed(() => bootstrap.value?.serviceVersion || "-");
|
||||
|
||||
async function load(force = false) {
|
||||
if (loaded && !force) return;
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const [bootstrapData, releaseData, sourceData, noticeData] = await Promise.allSettled([
|
||||
fetchJSON("/api/client/bootstrap"),
|
||||
fetchJSON("/api/client/releases"),
|
||||
fetchJSON("/api/client/sources"),
|
||||
fetchJSON("/api/client/notices"),
|
||||
]);
|
||||
if (bootstrapData.status === "fulfilled") bootstrap.value = bootstrapData.value;
|
||||
if (releaseData.status === "fulfilled") releases.value = releaseData.value;
|
||||
if (sourceData.status === "fulfilled") sources.value = sourceData.value;
|
||||
if (noticeData.status === "fulfilled") notices.value = noticeData.value.items || [];
|
||||
const firstFailure = [bootstrapData, releaseData, sourceData, noticeData].find((item) => item.status === "rejected") as PromiseRejectedResult | undefined;
|
||||
if (firstFailure && !bootstrap.value) error.value = firstFailure.reason?.message || String(firstFailure.reason);
|
||||
loaded = true;
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bootstrap,
|
||||
releases,
|
||||
sources,
|
||||
notices,
|
||||
loading,
|
||||
error,
|
||||
packages,
|
||||
categories,
|
||||
latestNotice,
|
||||
sourceCount,
|
||||
healthyCount,
|
||||
availability,
|
||||
downloadUrl,
|
||||
appVersion,
|
||||
databaseStatus,
|
||||
serviceVersion,
|
||||
load,
|
||||
sourceStatus,
|
||||
statusTone,
|
||||
formatBytes,
|
||||
};
|
||||
}
|
||||
|
||||
export function sourceStatus(item: any) {
|
||||
return item.health?.status || item.lastStatus || "unknown";
|
||||
}
|
||||
|
||||
export function statusTone(status: string) {
|
||||
const value = String(status || "").toLowerCase();
|
||||
if (["ok", "sqlite", "mysql", "online", "ready"].includes(value)) return "good";
|
||||
if (["degraded", "pending", "missing"].includes(value)) return "warn";
|
||||
if (["error", "offline", "failed"].includes(value)) return "bad";
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
export function formatBytes(value: number) {
|
||||
if (!Number.isFinite(value) || value <= 0) return "0 B";
|
||||
const units = ["B", "KB", "MB", "GB"];
|
||||
let next = value;
|
||||
let index = 0;
|
||||
while (next >= 1024 && index < units.length - 1) {
|
||||
next /= 1024;
|
||||
index += 1;
|
||||
}
|
||||
return `${next.toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
|
||||
}
|
||||
Reference in New Issue
Block a user