feat(ui): wire data.jsx to real API; add loading gate in app.jsx: data.jsx

This commit is contained in:
Zac Gaetano 2026-05-22 10:02:54 -04:00
parent 068e3a0828
commit 98025001e8

View file

@ -1,127 +1,175 @@
// data.jsx - mock data for Z-AMPP prototype
// exposed on window so all babel scripts can read it
// data.jsx API client; populates window.ZAMPP_DATA from real endpoints
const PROJECTS = [
{ id: "p1", name: "Protour 2026", assets: 142, updated: "2h ago", color: "#5B7CFA", thumbs: 4 },
{ id: "p2", name: "Studio A — Live", assets: 28, updated: "12m ago", color: "#FF3B30", thumbs: 3 },
{ id: "p3", name: "Sponsor Spots Q2", assets: 67, updated: "yesterday", color: "#2DD4A8", thumbs: 5 },
{ id: "p4", name: "Archive — 2025", assets: 1204, updated: "3w ago", color: "#B57CFA", thumbs: 6 },
{ id: "p5", name: "Documentary Cut", assets: 89, updated: "4d ago", color: "#F5A623", thumbs: 4 },
{ id: "p6", name: "Wild Dragon Promo", assets: 23, updated: "1h ago", color: "#FF8E5B", thumbs: 2 },
];
const API = '/api/v1';
const BINS = [
{ id: "b1", name: "All assets", count: 142, icon: "grid" },
{ id: "b2", name: "Live captures", count: 18, icon: "live" },
{ id: "b3", name: "Master files", count: 67, icon: "film" },
{ id: "b4", name: "Proxies", count: 142, icon: "proxy" },
{ id: "b5", name: "Audio stems", count: 24, icon: "audio" },
{ id: "b6", name: "Final deliverables", count: 8, icon: "package" },
];
window.ZAMPP_DATA = {
PROJECTS: [],
ASSETS: [],
RECORDERS: [],
JOBS: [],
NODES: [],
USERS: [],
BINS: [],
COMMENTS: [],
};
// generate a deterministic-ish thumb color
function thumbGrad(seed) {
const hues = [220, 280, 340, 30, 150, 200, 260, 320];
const h1 = hues[seed % hues.length];
const h2 = hues[(seed + 3) % hues.length];
return `linear-gradient(135deg, hsl(${h1} 45% 22%) 0%, hsl(${h2} 38% 12%) 100%)`;
async function apiFetch(path, opts = {}) {
const res = await fetch(API + path, {
credentials: 'include',
headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) },
...opts,
});
if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
return res.json();
}
const ASSETS = [
{ id: "a1", name: "Stage_Cam_A_Master.mov", type: "video", duration: "01:14:22", res: "3840×2160", fps: "59.94", codec: "ProRes 422", size: "187 GB", updated: "12m ago", status: "live", project: "Protour 2026", bin: "Live captures", comments: 3, seed: 1 },
{ id: "a2", name: "Sponsor_Reel_v3_FINAL.mp4", type: "video", duration: "00:00:30", res: "1920×1080", fps: "29.97", codec: "H.264", size: "412 MB", updated: "1h ago", status: "ready", project: "Sponsor Spots Q2", bin: "Final deliverables", comments: 8, seed: 2 },
{ id: "a3", name: "Interview_Driver_07.mxf", type: "video", duration: "00:23:45", res: "1920×1080", fps: "25", codec: "XDCAM HD", size: "8.4 GB", updated: "3h ago", status: "processing", project: "Documentary Cut", bin: "Master files", comments: 0, seed: 3, progress: 67 },
{ id: "a4", name: "Wide_Cam_B_proxy.mp4", type: "video", duration: "00:45:10", res: "1280×720", fps: "59.94", codec: "H.264", size: "1.2 GB", updated: "12m ago", status: "ready", project: "Protour 2026", bin: "Proxies", comments: 2, seed: 4 },
{ id: "a5", name: "Drone_Aerial_Lap_3.mov", type: "video", duration: "00:04:22", res: "3840×2160", fps: "59.94", codec: "ProRes 422 HQ", size: "12 GB", updated: "4h ago", status: "ready", project: "Wild Dragon Promo", bin: "Master files", comments: 5, seed: 5 },
{ id: "a6", name: "Audio_FOH_Mix.wav", type: "audio", duration: "01:14:22", res: "—", fps: "—", codec: "PCM 48kHz", size: "1.6 GB", updated: "12m ago", status: "live", project: "Protour 2026", bin: "Audio stems", comments: 1, seed: 6 },
{ id: "a7", name: "Trophy_Ceremony.mov", type: "video", duration: "00:18:32", res: "3840×2160", fps: "29.97", codec: "ProRes 422", size: "42 GB", updated: "yesterday", status: "ready", project: "Protour 2026", bin: "Master files", comments: 12, seed: 7 },
{ id: "a8", name: "Pit_Lane_GoPro_1.mp4", type: "video", duration: "00:12:08", res: "2704×1520", fps: "59.94", codec: "H.265", size: "3.8 GB", updated: "yesterday", status: "ready", project: "Protour 2026", bin: "All assets", comments: 0, seed: 8 },
{ id: "a9", name: "Sponsor_Logo_Lower3rd.mov", type: "video", duration: "00:00:08", res: "1920×1080", fps: "30", codec: "ProRes 4444", size: "82 MB", updated: "2d ago", status: "ready", project: "Sponsor Spots Q2", bin: "All assets", comments: 0, seed: 9 },
{ id: "a10", name: "Backstage_Walkthrough.mxf", type: "video", duration: "00:08:14", res: "1920×1080", fps: "25", codec: "XDCAM HD", size: "2.9 GB", updated: "3d ago", status: "error", project: "Documentary Cut", bin: "Master files", comments: 1, seed: 10 },
{ id: "a11", name: "Pre_Race_Press.mov", type: "video", duration: "00:34:50", res: "3840×2160", fps: "25", codec: "ProRes 422", size: "78 GB", updated: "3d ago", status: "ready", project: "Documentary Cut", bin: "Master files", comments: 4, seed: 11 },
{ id: "a12", name: "Wide_Cam_C_Master.mov", type: "video", duration: "01:12:00", res: "3840×2160", fps: "59.94", codec: "ProRes 422", size: "172 GB", updated: "5d ago", status: "ready", project: "Protour 2026", bin: "Master files", comments: 0, seed: 12 },
];
function fmtDuration(ms) {
if (!ms) return '—';
const s = Math.round(ms / 1000);
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
if (h > 0) return h + ':' + String(m).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
return m + ':' + String(sec).padStart(2, '0');
}
const RECORDERS = [
{ id: "r1", name: "Stage Cam A", source: "SDI", url: "DeckLink Duo 2 · Port 1", node: "zampp2", status: "recording", elapsed: "01:14:22", bitrate: "120 Mbps", res: "2160p59.94", codec: "ProRes 422 HQ", health: 98 },
{ id: "r2", name: "Wide Cam B", source: "SRT", url: "srt://10.0.4.18:4201", node: "zampp1", status: "recording", elapsed: "00:45:10", bitrate: "45 Mbps", res: "1080p59.94", codec: "H.265", health: 91 },
{ id: "r3", name: "FOH Audio Feed", source: "SRT", url: "srt://10.0.4.18:4202", node: "zampp1", status: "recording", elapsed: "01:14:22", bitrate: "1.5 Mbps", res: "—", codec: "AAC 48k", health: 100, audio: true },
{ id: "r4", name: "Pit Lane RTMP", source: "RTMP", url: "rtmp://stream.local/pit", node: "zampp1", status: "idle", elapsed: "00:00:00", bitrate: "—", res: "1080p30", codec: "H.264", health: 0 },
{ id: "r5", name: "Trophy Cam", source: "SDI", url: "DeckLink Duo 2 · Port 2", node: "zampp2", status: "armed", elapsed: "00:00:00", bitrate: "—", res: "1080p59.94", codec: "ProRes 422", health: 100 },
{ id: "r6", name: "Drone Downlink", source: "SRT", url: "srt://10.0.4.18:4204", node: "zampp1", status: "error", elapsed: "00:02:14", bitrate: "—", res: "—", codec: "H.265", health: 12 },
];
function fmtSize(bytes) {
if (!bytes) return '—';
if (bytes >= 1e12) return (bytes / 1e12).toFixed(1) + ' TB';
if (bytes >= 1e9) return (bytes / 1e9).toFixed(1) + ' GB';
if (bytes >= 1e6) return Math.round(bytes / 1e6) + ' MB';
return Math.round(bytes / 1e3) + ' KB';
}
const JOBS = [
{ id: "j1", kind: "Proxy", asset: "Stage_Cam_A_Master.mov", node: "zampp1", progress: 67, eta: "08:42", status: "running", priority: "high", started: "12m ago" },
{ id: "j2", kind: "Transcode", asset: "Sponsor_Reel_v3.mp4", node: "zampp1", progress: 34, eta: "02:18", status: "running", priority: "normal", started: "4m ago" },
{ id: "j3", kind: "Thumbnail", asset: "Interview_Driver_07.mxf", node: "zampp2", progress: 92, eta: "00:08", status: "running", priority: "normal", started: "1m ago" },
{ id: "j4", kind: "Proxy", asset: "Wide_Cam_C_Master.mov", node: "zampp1", progress: 100, eta: "—", status: "done", priority: "normal", started: "18m ago" },
{ id: "j5", kind: "AMPP Sync", asset: "Trophy_Ceremony.mov", node: "zampp1", progress: 100, eta: "—", status: "done", priority: "high", started: "1h ago" },
{ id: "j6", kind: "Transcode", asset: "Backstage_Walkthrough.mxf", node: "zampp2", progress: 0, eta: "—", status: "failed", priority: "normal", started: "2h ago", error: "Codec mismatch" },
{ id: "j7", kind: "Proxy", asset: "Pre_Race_Press.mov", node: "zampp1", progress: 0, eta: "—", status: "queued", priority: "normal", started: "queued" },
{ id: "j8", kind: "Proxy", asset: "Drone_Aerial_Lap_3.mov", node: "zampp1", progress: 0, eta: "—", status: "queued", priority: "low", started: "queued" },
];
function fmtRelative(iso) {
if (!iso) return '—';
const diff = (Date.now() - new Date(iso)) / 1000;
if (diff < 60) return 'just now';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
return Math.floor(diff / 86400) + 'd ago';
}
const NODES = [
{ id: "zampp1", role: "primary", ip: "172.18.91.216", cpu: 12, mem: 21, memTotal: 96, gpus: ["NVIDIA RTX A4000", "NVIDIA RTX A4000", "NVIDIA RTX A4000"], version: "1.2.0", status: "online", uptime: "13d 4h", x: 0.5, y: 0.5 },
{ id: "zampp2", role: "worker", ip: "172.18.91.217", cpu: 34, mem: 6.8, memTotal: 19, gpus: ["NVIDIA T1000"], version: "1.2.0", status: "online", uptime: "13d 4h", devices: ["DeckLink Duo 2"], x: 0.22, y: 0.28 },
{ id: "zampp3", role: "capture", ip: "172.18.91.218", cpu: 8, mem: 4.2, memTotal: 32, gpus: ["NVIDIA T400"], version: "1.2.0", status: "online", uptime: "2d 8h", devices: ["DeckLink Quad HDMI"], x: 0.22, y: 0.72 },
{ id: "zampp4", role: "worker", ip: "172.18.91.219", cpu: 0, mem: 0.4, memTotal: 32, gpus: [], version: "1.1.4", status: "offline", uptime: "—", x: 0.78, y: 0.28 },
{ id: "zampp5", role: "worker", ip: "172.18.91.220", cpu: 18, mem: 9.1, memTotal: 64, gpus: ["NVIDIA RTX A2000"], version: "1.2.0", status: "online", uptime: "5h 12m", x: 0.78, y: 0.72 },
];
const PROJECT_COLORS = ['#5B7CFA', '#2DD4A8', '#FF5B5B', '#F5A623', '#B57CFA', '#6B7280'];
const CONTAINERS = [
{ id: "c1", name: "wild-dragon-mam-api", image: "wild-dragon-mam-api:1.2.0", state: "running", uptime: "1h 12m", ports: "47432:3000/tcp", cpu: 4.2, mem: 312 },
{ id: "c2", name: "wild-dragon-web-ui", image: "wild-dragon-web-ui:1.2.0", state: "running", uptime: "4h 18m", ports: "47434:80/tcp", cpu: 0.4, mem: 48 },
{ id: "c3", name: "wild-dragon-editor", image: "wild-dragon-editor:0.4.1", state: "running", uptime: "28h", ports: "47435:80/tcp", cpu: 0.1, mem: 32 },
{ id: "c4", name: "wild-dragon-capture", image: "wild-dragon-capture:1.2.0", state: "running", uptime: "28h", ports: "47433:3001, 49000:9000/udp, 41935:1935", cpu: 8.7, mem: 421 },
{ id: "c5", name: "wild-dragon-worker", image: "wild-dragon-worker:1.2.0", state: "running", uptime: "13h", ports: "—", cpu: 12.4, mem: 824 },
{ id: "c6", name: "wild-dragon-queue", image: "redis:7-alpine", state: "running", uptime: "13h", ports: "—", cpu: 0.2, mem: 18 },
{ id: "c7", name: "wild-dragon-db", image: "postgres:16", state: "running", uptime: "13h", ports: "—", cpu: 1.1, mem: 184, healthy: true },
];
function normalizeAsset(a, projectMap) {
return {
...a,
name: a.display_name || a.filename || 'Untitled',
type: a.media_type || 'video',
duration: fmtDuration(a.duration_ms),
size: fmtSize(a.file_size),
res: a.resolution || '—',
updated: fmtRelative(a.updated_at),
project: (projectMap && projectMap[a.project_id]) || '',
comments: 0,
progress: 0,
tc: a.start_tc || null,
seed: a.id ? a.id.charCodeAt(0) % 6 : 1,
};
}
const USERS = [
{ id: "u1", username: "admin", display: "Zach Gaetano", role: "admin", groups: ["broadcast"], last: "now", avatar: "ZG" },
{ id: "u2", username: "k.morales", display: "Kira Morales", role: "editor", groups: ["broadcast", "post"], last: "8m ago", avatar: "KM" },
{ id: "u3", username: "j.tran", display: "Jules Tran", role: "ingest", groups: ["broadcast"], last: "1h ago", avatar: "JT" },
{ id: "u4", username: "m.okafor", display: "Mara Okafor", role: "viewer", groups: ["clients"], last: "2d ago", avatar: "MO" },
{ id: "u5", username: "svc.ampp", display: "AMPP Service", role: "service", groups: ["system"], last: "now", avatar: "AM" },
];
function normalizeRecorder(r) {
let elapsed = '—';
if (r.status === 'recording' && r.started_at) {
const s = Math.floor((Date.now() - new Date(r.started_at)) / 1000);
elapsed = String(Math.floor(s / 3600)).padStart(2, '0') + ':' +
String(Math.floor((s % 3600) / 60)).padStart(2, '0') + ':' +
String(s % 60).padStart(2, '0');
}
const cfg = r.source_config || {};
return {
...r,
source: r.source_type || '—',
url: cfg.url || cfg.address || cfg.srt_url || cfg.rtmp_url || r.source_type || '—',
codec: r.recording_codec || '—',
res: r.recording_resolution || '—',
node: r.node_id ? r.node_id.slice(0, 8) : 'primary',
elapsed,
bitrate: '—',
health: 100,
audio: false,
};
}
const TOKENS_LIST = [
{ id: "t1", name: "AMPP Sync — Production", scope: "assets:write, sync:write", created: "2026-04-12", last: "2m ago", owner: "svc.ampp" },
{ id: "t2", name: "Editor CI", scope: "editor:render", created: "2026-03-01", last: "1h ago", owner: "k.morales" },
{ id: "t3", name: "Read-only audit", scope: "assets:read", created: "2026-02-18", last: "yesterday", owner: "admin" },
{ id: "t4", name: "Old token (revoked)", scope: "—", created: "2025-11-04", last: "—", owner: "admin", revoked: true },
];
function normalizeJob(j) {
const statusMap = { waiting: 'queued', active: 'running', completed: 'done', failed: 'failed' };
const kindMap = { proxy: 'Proxy', thumbnail: 'Thumbnail', conform: 'Conform', transcode: 'Transcode' };
const meta = j.metadata || {};
return {
...j,
status: statusMap[j.status] || j.status,
kind: kindMap[j.type] || j.type || 'Job',
asset: j.asset_name || meta.filename || '—',
eta: '—',
node: meta.node || '—',
priority: meta.priority || 'normal',
error: j.error || null,
progress: j.progress || 0,
};
}
const ACTIVITY = [
{ id: "ac1", who: "Kira Morales", what: "added a comment on", target: "Stage_Cam_A_Master.mov", time: "2m ago", kind: "comment" },
{ id: "ac2", who: "Stage Cam A", what: "started recording", target: "01:14:22 elapsed", time: "1h 14m ago", kind: "record" },
{ id: "ac3", who: "Proxy job", what: "completed for", target: "Wide_Cam_C_Master.mov", time: "18m ago", kind: "job" },
{ id: "ac4", who: "Jules Tran", what: "uploaded", target: "Drone_Aerial_Lap_3.mov", time: "4h ago", kind: "upload" },
{ id: "ac5", who: "AMPP Service", what: "synced", target: "Trophy_Ceremony.mov", time: "1h ago", kind: "sync" },
{ id: "ac6", who: "Drone Downlink", what: "errored —", target: "signal lost", time: "12m ago", kind: "error" },
{ id: "ac7", who: "Kira Morales", what: "marked as final", target: "Sponsor_Reel_v3_FINAL.mp4", time: "1h ago", kind: "approve" },
];
async function loadData() {
const [projectsR, assetsR, recordersR, jobsR, clusterR, usersR, binsR] = await Promise.allSettled([
apiFetch('/projects'),
apiFetch('/assets?limit=500'),
apiFetch('/recorders'),
apiFetch('/jobs'),
apiFetch('/cluster'),
apiFetch('/users'),
apiFetch('/bins'),
]);
const COMMENTS = [
{ id: "cm1", who: "Kira Morales", avatar: "KM", time: "00:12:04", real: "12m ago", text: "Hit point on the engine roar feels late — can we slip the cut 4 frames earlier?", resolved: false, frame: 12 * 60 * 30 + 4 * 30 },
{ id: "cm2", who: "Zach Gaetano", avatar: "ZG", time: "00:24:18", real: "8m ago", text: "Sponsor logo is clipping into safe area. Bump 6px inward.", resolved: false, frame: 24 * 60 * 30 + 18 * 30 },
{ id: "cm3", who: "Mara Okafor", avatar: "MO", time: "00:41:55", real: "5m ago", text: "Approved from sponsor side. Ready to publish ✓", resolved: true, frame: 41 * 60 * 30 + 55 * 30 },
{ id: "cm4", who: "Kira Morales", avatar: "KM", time: "01:02:30", real: "2m ago", text: "Audio dip here — FOH mix dropped 6dB. Pulling stem fix from r3.", resolved: false, frame: 62 * 60 * 30 + 30 * 30 },
];
const projectMap = {};
if (projectsR.status === 'fulfilled') {
window.ZAMPP_DATA.PROJECTS = (projectsR.value || []).map((p, i) => ({
...p,
color: PROJECT_COLORS[i % PROJECT_COLORS.length],
assets: 0,
updated: fmtRelative(p.updated_at),
}));
window.ZAMPP_DATA.PROJECTS.forEach(p => { projectMap[p.id] = p.name; });
}
const SDI_PORTS_zampp2 = [
{ idx: 1, label: "Stage Cam A", active: true, signal: "2160p59.94", level: 0.84, recording: true },
{ idx: 2, label: "Stage Cam B", active: true, signal: "2160p59.94", level: 0.77, recording: false },
{ idx: 3, label: "Trophy Cam", active: true, signal: "1080p59.94", level: 0.68, recording: false },
{ idx: 4, label: "—", active: false, signal: "no signal", level: 0, recording: false },
];
if (assetsR.status === 'fulfilled') {
const raw = assetsR.value;
const list = Array.isArray(raw) ? raw : (raw.assets || []);
window.ZAMPP_DATA.ASSETS = list.map(a => normalizeAsset(a, projectMap));
const counts = {};
list.forEach(a => { if (a.project_id) counts[a.project_id] = (counts[a.project_id] || 0) + 1; });
window.ZAMPP_DATA.PROJECTS = window.ZAMPP_DATA.PROJECTS.map(p => ({ ...p, assets: counts[p.id] || 0 }));
}
Object.assign(window, {
ZAMPP_DATA: {
PROJECTS, BINS, ASSETS, RECORDERS, JOBS, NODES, CONTAINERS, USERS,
TOKENS_LIST, ACTIVITY, COMMENTS, SDI_PORTS_zampp2, thumbGrad,
},
});
if (recordersR.status === 'fulfilled') {
window.ZAMPP_DATA.RECORDERS = (recordersR.value || []).map(normalizeRecorder);
}
if (jobsR.status === 'fulfilled') {
window.ZAMPP_DATA.JOBS = (jobsR.value || []).map(normalizeJob);
}
if (clusterR.status === 'fulfilled') {
window.ZAMPP_DATA.NODES = clusterR.value || [];
}
if (usersR.status === 'fulfilled') {
window.ZAMPP_DATA.USERS = (usersR.value || []).map(u => ({
...u,
name: u.display_name || u.username || u.email || 'Unknown',
initials: (u.display_name || u.username || '?').slice(0, 2).toUpperCase(),
role: u.role || 'viewer',
joined: fmtRelative(u.created_at),
lastSeen: fmtRelative(u.last_seen || u.updated_at),
}));
}
if (binsR.status === 'fulfilled') {
window.ZAMPP_DATA.BINS = (binsR.value || []).map(b => ({
...b,
count: b.asset_count || 0,
icon: b.type || 'grid',
}));
}
}
window.ZAMPP_API = { fetch: apiFetch, loadData, fmtDuration, fmtSize, fmtRelative };