AEB-71: Added 3D navigation in monitoring zones

This commit is contained in:
iv_vuytsik
2025-11-11 10:07:38 +03:00
parent 549a05509b
commit 88653cb07c
27 changed files with 503 additions and 184 deletions

View File

@@ -17,11 +17,21 @@ export async function GET(
const stat = fs.statSync(filePath);
const stream = fs.createReadStream(filePath);
const ext = path.extname(fileName).toLowerCase();
let contentType = 'application/octet-stream';
if (ext === '.glb') contentType = 'model/gltf-binary';
else if (ext === '.gltf') contentType = 'model/gltf+json';
else if (ext === '.bin') contentType = 'application/octet-stream';
else if (ext === '.png') contentType = 'image/png';
else if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg';
else if (ext === '.webp') contentType = 'image/webp';
else if (ext === '.ktx2') contentType = 'image/ktx2';
return new Response(stream as unknown as ReadableStream, {
headers: {
'Content-Length': stat.size.toString(),
'Content-Type': 'model/gltf-binary',
'Content-Type': contentType,
},
});
}

View File

@@ -0,0 +1,47 @@
export const dynamic = 'force-static';
import fs from 'fs';
import path from 'path';
export async function GET() {
try {
const dirPath = path.join(process.cwd(), 'assets', 'big-models');
if (!fs.existsSync(dirPath)) {
return new Response(JSON.stringify({ models: [] }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
const files = fs.readdirSync(dirPath, { withFileTypes: true });
const models = files
.filter((ent) => ent.isFile() && (ent.name.toLowerCase().endsWith('.glb') || ent.name.toLowerCase().endsWith('.gltf')))
.map((ent) => {
const filename = ent.name;
const base = filename.replace(/\.(glb|gltf)$/i, '');
let title = base;
title = title.replace(/^AerBIM-Monitor_ASM-HT-Viewer_/i, '');
title = title.replace(/_/g, ' ');
title = title.replace(/\bLevel\b/gi, 'Уровень');
title = title.replace(/\bcustom\s*prop\b/gi, '');
title = title.replace(/\bcustom\b/gi, '');
title = title.replace(/\bprop\b/gi, '');
title = title.replace(/\s{2,}/g, ' ').trim();
const pathUrl = `/static-models/${filename}`;
return { name: title, path: pathUrl };
});
return new Response(JSON.stringify({ models }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error('[big-models/list] Error listing models:', error);
const msg = error instanceof Error ? error.message : String(error);
return new Response(JSON.stringify({ error: msg, models: [] }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}

View File

@@ -89,16 +89,21 @@ export async function GET(req: NextRequest) {
return true
}) : list
const transformed = filtered.map((a) => ({
id: a.id,
detector_name: a.name,
message: a.message,
type: a.severity,
location: a.object,
priority: a.severity,
acknowledged: a.resolved,
timestamp: a.created_at,
}))
const transformed = filtered.map((a) => {
const severity = String(a?.severity || a?.type || '').toLowerCase()
const type = severity === 'critical' ? 'critical' : severity === 'warning' ? 'warning' : 'info'
const priority = severity === 'critical' ? 'high' : severity === 'warning' ? 'medium' : 'low'
return {
id: a.id,
detector_name: a.name || a.detector_name,
message: a.message,
type,
location: a.object,
priority,
acknowledged: typeof a.acknowledged === 'boolean' ? a.acknowledged : !!a.resolved,
timestamp: a.timestamp || a.created_at,
}
})
return NextResponse.json({ success: true, data: transformed })
} catch (error) {

View File

@@ -36,10 +36,16 @@ export async function GET(req: NextRequest) {
if (!backendUrl) {
return NextResponse.json({ success: false, error: 'BACKEND_URL is not configured' }, { status: 500 })
}
const url = new URL(req.url)
const timePeriod = url.searchParams.get('time_period')
const qs = timePeriod ? `?time_period=${encodeURIComponent(timePeriod)}` : ''
const timePeriodRaw = url.searchParams.get('time_period')
const allowedPeriods = new Set([24, 72, 168, 720])
let timePeriodNum = timePeriodRaw ? Number(timePeriodRaw) : undefined
if (Number.isNaN(timePeriodNum)) {
timePeriodNum = undefined
}
const finalTimePeriod = timePeriodNum && allowedPeriods.has(timePeriodNum) ? String(timePeriodNum) : '168'
const qs = `?time_period=${encodeURIComponent(finalTimePeriod)}`
const res = await fetch(`${backendUrl}/account/get-dashboard/${qs}`, {
headers: {

View File

@@ -67,6 +67,20 @@ export async function GET() {
checked: sensor.checked ?? false,
location: sensor.zone ?? '',
serial_number: sensor.serial_number ?? sensor.name ?? '',
detector_type: sensor.detector_type ?? '',
notifications: Array.isArray(sensor.notifications) ? sensor.notifications.map((n: any) => {
const severity = String(n?.severity || n?.type || '').toLowerCase()
const type = severity === 'critical' ? 'critical' : severity === 'warning' ? 'warning' : 'info'
const priority = severity === 'critical' ? 'high' : severity === 'warning' ? 'medium' : 'low'
return {
id: n.id,
type,
message: n.message,
timestamp: n.timestamp || n.created_at,
acknowledged: typeof n.acknowledged === 'boolean' ? n.acknowledged : !!n.resolved,
priority,
}
}) : []
}
}

View File

@@ -23,11 +23,32 @@ export async function GET(req: NextRequest) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: refreshToken }),
})
if (refreshRes.ok) {
const refreshed = await refreshRes.json()
accessToken = refreshed.access
} else {
const errorText = await refreshRes.text()
let errorData: { error?: string; detail?: string; code?: string } = {}
try {
errorData = JSON.parse(errorText)
} catch {
errorData = { error: errorText }
}
const errorMessage = (errorData.error as string) || (errorData.detail as string) || ''
if (typeof errorMessage === 'string' &&
(errorMessage.includes('Token is expired') ||
errorMessage.includes('expired') ||
errorData.code === 'token_not_valid')) {
console.warn('Refresh token expired, user needs to re-authenticate')
} else {
console.error('Token refresh failed:', errorData.error || errorData.detail || 'Unknown error')
}
}
} catch {}
} catch (error) {
console.error('Error during token refresh:', error)
}
}
if (!accessToken) {
@@ -52,7 +73,21 @@ export async function GET(req: NextRequest) {
let payload: any
try { payload = JSON.parse(payloadText) } catch { payload = payloadText }
if (!objectsRes.ok) {
if (!objectsRes.ok) {
if (payload && typeof payload === 'object') {
if (payload.code === 'token_not_valid' ||
(payload.detail && typeof payload.detail === 'string' && (payload.detail.includes('Token is expired') || payload.detail.includes('Given token not valid'))) ||
(payload.messages && Array.isArray(payload.messages) && payload.messages.some((msg: any) =>
msg.message && typeof msg.message === 'string' && msg.message.includes('Token is expired')
))) {
console.warn('Access token expired, user needs to re-authenticate')
return NextResponse.json({
success: false,
error: 'Authentication required - please log in again'
}, { status: 401 })
}
}
const err = typeof payload === 'string' ? payload : JSON.stringify(payload)
return NextResponse.json({ success: false, error: `Backend objects error: ${err}` }, { status: objectsRes.status })
}