9740bc64c9
Continuous Deployment / Deploy to Production (push) Blocked by required conditions
Continuous Deployment / Rollback Deployment (push) Blocked by required conditions
Continuous Deployment / Post-deployment Monitoring (push) Blocked by required conditions
Continuous Deployment / Notify Deployment Status (push) Blocked by required conditions
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Continuous Deployment / Deploy to Staging (push) Waiting to run
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Security Report (push) Waiting to run
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
149 lines
4.8 KiB
JavaScript
149 lines
4.8 KiB
JavaScript
// Data Export Utility - Export sensor/pose data as JSON or CSV
|
|
|
|
import { sensingService } from '../services/sensing.service.js';
|
|
import { toastManager } from './toast.js';
|
|
|
|
export class DataExport {
|
|
constructor() {
|
|
this.buffer = [];
|
|
this.maxBuffer = 1000;
|
|
this.recording = false;
|
|
this._unsub = null;
|
|
}
|
|
|
|
init() {
|
|
document.addEventListener('export-data', () => this.showExportDialog());
|
|
|
|
// Continuously buffer sensing data when available
|
|
this._unsub = sensingService.onData((data) => {
|
|
if (this.buffer.length >= this.maxBuffer) {
|
|
this.buffer.shift();
|
|
}
|
|
this.buffer.push({
|
|
timestamp: new Date().toISOString(),
|
|
...this.extractFields(data)
|
|
});
|
|
});
|
|
}
|
|
|
|
extractFields(data) {
|
|
// Extract relevant fields from sensing data
|
|
return {
|
|
rssi: data.rssi ?? null,
|
|
variance: data.variance ?? null,
|
|
motion_band: data.motion_band ?? null,
|
|
breathing_band: data.breathing_band ?? null,
|
|
classification: data.classification ?? null,
|
|
person_count: data.person_count ?? data.persons ?? null,
|
|
subcarriers: data.subcarrier_count ?? null,
|
|
source: data.source ?? null
|
|
};
|
|
}
|
|
|
|
showExportDialog() {
|
|
if (this.buffer.length === 0) {
|
|
toastManager.warning('No sensor data to export. Connect to a data source first.');
|
|
return;
|
|
}
|
|
|
|
// Create dialog
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'export-dialog-overlay';
|
|
overlay.innerHTML = `
|
|
<div class="export-dialog" role="dialog" aria-label="Export data" aria-modal="true">
|
|
<h3>Export Sensor Data</h3>
|
|
<p class="export-dialog-info">${this.buffer.length} data points available</p>
|
|
<div class="export-dialog-options">
|
|
<label class="export-option">
|
|
<input type="radio" name="export-format" value="json" checked>
|
|
<span>JSON</span>
|
|
<small>Full data with nested fields</small>
|
|
</label>
|
|
<label class="export-option">
|
|
<input type="radio" name="export-format" value="csv">
|
|
<span>CSV</span>
|
|
<small>Flat table, spreadsheet-ready</small>
|
|
</label>
|
|
</div>
|
|
<div class="export-dialog-range">
|
|
<label>
|
|
Last <input type="number" id="export-count" value="${Math.min(this.buffer.length, 500)}" min="1" max="${this.buffer.length}"> data points
|
|
</label>
|
|
</div>
|
|
<div class="export-dialog-actions">
|
|
<button class="btn btn--secondary export-cancel">Cancel</button>
|
|
<button class="btn btn--primary export-confirm">Export</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
overlay.addEventListener('click', (e) => {
|
|
if (e.target === overlay) overlay.remove();
|
|
});
|
|
overlay.querySelector('.export-cancel').addEventListener('click', () => overlay.remove());
|
|
overlay.querySelector('.export-confirm').addEventListener('click', () => {
|
|
const format = overlay.querySelector('input[name="export-format"]:checked').value;
|
|
const count = parseInt(overlay.querySelector('#export-count').value, 10) || this.buffer.length;
|
|
this.exportData(format, count);
|
|
overlay.remove();
|
|
});
|
|
|
|
document.body.appendChild(overlay);
|
|
overlay.querySelector('.export-confirm').focus();
|
|
}
|
|
|
|
exportData(format, count) {
|
|
const data = this.buffer.slice(-count);
|
|
|
|
let content, filename, mimeType;
|
|
|
|
if (format === 'json') {
|
|
content = JSON.stringify(data, null, 2);
|
|
filename = `ruview-data-${this.timestamp()}.json`;
|
|
mimeType = 'application/json';
|
|
} else {
|
|
content = this.toCSV(data);
|
|
filename = `ruview-data-${this.timestamp()}.csv`;
|
|
mimeType = 'text/csv';
|
|
}
|
|
|
|
this.downloadFile(content, filename, mimeType);
|
|
toastManager.success(`Exported ${data.length} data points as ${format.toUpperCase()}`);
|
|
}
|
|
|
|
toCSV(data) {
|
|
if (data.length === 0) return '';
|
|
const headers = Object.keys(data[0]);
|
|
const rows = data.map(row => headers.map(h => {
|
|
const val = row[h];
|
|
if (val === null || val === undefined) return '';
|
|
if (typeof val === 'string' && (val.includes(',') || val.includes('"'))) {
|
|
return `"${val.replace(/"/g, '""')}"`;
|
|
}
|
|
return String(val);
|
|
}).join(','));
|
|
return [headers.join(','), ...rows].join('\n');
|
|
}
|
|
|
|
downloadFile(content, filename, mimeType) {
|
|
const blob = new Blob([content], { type: mimeType });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.style.display = 'none';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
timestamp() {
|
|
return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
}
|
|
|
|
dispose() {
|
|
if (this._unsub) this._unsub();
|
|
}
|
|
}
|