fix: rewrite Synology API to match actual DSM field names

- Volume sizes are in vol.size.total / vol.size.used (nested object)
- Added disk info parsing (name, model, status, temp, isSsd)
- Added SYNO.Core.System.Utilization for CPU/memory stats
- Response now returns {volumes, disks, utilization} matching widget interface
This commit is contained in:
2026-02-17 09:02:32 -05:00
parent 12d71bc6b1
commit df1f0700b0

View File

@@ -10,6 +10,8 @@ const SYNOLOGY_PASSWORD = process.env.SYNOLOGY_PASSWORD;
// Port 5000 = HTTP, 5001+ = HTTPS (Synology convention) // Port 5000 = HTTP, 5001+ = HTTPS (Synology convention)
const protocol = SYNOLOGY_PORT === "5000" ? "http" : "https"; const protocol = SYNOLOGY_PORT === "5000" ? "http" : "https";
export const dynamic = "force-dynamic";
export async function GET() { export async function GET() {
if (!SYNOLOGY_HOST || !SYNOLOGY_USERNAME || !SYNOLOGY_PASSWORD) { if (!SYNOLOGY_HOST || !SYNOLOGY_USERNAME || !SYNOLOGY_PASSWORD) {
return NextResponse.json( return NextResponse.json(
@@ -22,12 +24,10 @@ export async function GET() {
const baseUrl = `${protocol}://${SYNOLOGY_HOST}:${SYNOLOGY_PORT}`; const baseUrl = `${protocol}://${SYNOLOGY_HOST}:${SYNOLOGY_PORT}`;
const httpsConfig = const httpsConfig =
protocol === "https" protocol === "https"
? { ? { httpsAgent: new https.Agent({ rejectUnauthorized: false }) }
httpsAgent: new https.Agent({ rejectUnauthorized: false }),
}
: {}; : {};
// Login to Synology // Login
const loginResponse = await axios.get(`${baseUrl}/webapi/auth.cgi`, { const loginResponse = await axios.get(`${baseUrl}/webapi/auth.cgi`, {
params: { params: {
api: "SYNO.API.Auth", api: "SYNO.API.Auth",
@@ -43,8 +43,9 @@ export async function GET() {
const sid = loginResponse.data.data.sid; const sid = loginResponse.data.data.sid;
// Get storage info // Fetch storage + utilization in parallel
const storageResponse = await axios.get(`${baseUrl}/webapi/entry.cgi`, { const [storageResponse, utilizationResponse] = await Promise.all([
axios.get(`${baseUrl}/webapi/entry.cgi`, {
params: { params: {
api: "SYNO.Storage.CGI.Storage", api: "SYNO.Storage.CGI.Storage",
version: 1, version: 1,
@@ -52,21 +53,67 @@ export async function GET() {
_sid: sid, _sid: sid,
}, },
...httpsConfig, ...httpsConfig,
}),
axios.get(`${baseUrl}/webapi/entry.cgi`, {
params: {
api: "SYNO.Core.System.Utilization",
version: 1,
method: "get",
_sid: sid,
},
...httpsConfig,
}).catch(() => null),
]);
const storageData = storageResponse.data.data;
// Parse volumes — size info is nested: vol.size.total, vol.size.used
const rawVolumes = storageData.volumes || [];
const volumes = rawVolumes.map((vol: any) => {
const sizeObj = vol.size || {};
const total = parseInt(sizeObj.total, 10) || 0;
const used = parseInt(sizeObj.used, 10) || 0;
const available = total - used;
const pct = total > 0 ? ((used / total) * 100).toFixed(2) : "0";
return {
volume: vol.vol_path || vol.volume_path || "",
id: vol.id || "",
size: total,
used: used,
available: available,
percentUsed: pct,
status: vol.status || "unknown",
fsType: vol.fs_type || "",
};
}); });
const volumes = storageResponse.data.data.volumes.map((vol: any) => ({ // Parse disks
volume: vol.volume_path, const rawDisks = storageData.disks || [];
size: vol.size_total_byte, const disks = rawDisks.map((d: any) => ({
used: vol.size_used_byte, name: d.name || d.longName || "Unknown",
available: vol.size_free_byte, model: d.model || "",
percentUsed: ((vol.size_used_byte / vol.size_total_byte) * 100).toFixed( status: d.smart_status || d.status || "unknown",
2 isSsd: d.isSsd ?? false,
), temp: typeof d.temp === "number" ? d.temp : null,
})); }));
return NextResponse.json(volumes); // Parse utilization
let utilization: { cpu: number | null; memory: number | null } | null = null;
if (utilizationResponse?.data?.data) {
const util = utilizationResponse.data.data;
const cpu = util.cpu
? (util.cpu.user_load || 0) + (util.cpu.system_load || 0)
: null;
const memory = util.memory?.real_usage ?? null;
utilization = { cpu, memory };
}
return NextResponse.json({ volumes, disks, utilization });
} catch (error) { } catch (error) {
console.error("Synology API error:", error instanceof Error ? error.message : error); console.error(
"Synology API error:",
error instanceof Error ? error.message : error
);
return NextResponse.json( return NextResponse.json(
{ error: "Failed to fetch Synology storage" }, { error: "Failed to fetch Synology storage" },
{ status: 500 } { status: 500 }