Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 105 additions & 31 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -297,43 +297,117 @@ <h3 class="group-header">Cloud Files</h3>
<div id="fileInfo" class="status-text-subtle"></div>
</div>

<div id="projectHistoryContainer" class="control-group gray-box">
<div class="group-header-row">
<h3 id="projectNameDisplay">Project History</h3>
<button
class="btn-icon btn-rename-project"
onclick="
event.stopPropagation();
editProjectName();
"
title="Rename Project"
>
<i class="fas fa-pen"></i>
</button>
<div
id="projectUnifiedPanel"
class="control-group gray-box"
style="
display: flex;
flex-direction: column;
gap: 16px;
padding: 12px;
"
>
<div
class="group-header-row"
style="
display: flex;
justify-content: space-between;
align-items: flex-end;
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
cursor: pointer;
"
>
<div>
<span
style="
display: block;
font-size: 0.7em;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 2px;
"
>Active Session</span
>
<h3
id="projectNameDisplay"
style="
margin: 0;
font-size: 1.1em;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
"
>
Default Project
</h3>
</div>
<div style="display: flex; gap: 6px">
<button
class="btn-icon"
onclick="editProjectName()"
title="Rename Project"
style="
width: 24px;
height: 24px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
"
>
<i class="fas fa-pen" style="font-size: 0.8em"></i>
</button>
</div>
</div>

<p class="section-description">Timeline of analysis steps.</p>

<div id="projectHistoryList" class="history-list"></div>
<div id="librarySlot"></div>

<div class="flex-row-gap-5">
<button
id="btnReplayProject"
class="btn btn-sm btn-primary flex-1"
onclick="replayProjectHistory()"
>
<i class="fas fa-play"></i> Replay All
</button>
<button
class="btn btn-sm"
onclick="resetProject()"
title="Clear History"
<div
id="historySlot"
style="
border-top: 1px solid var(--border-color);
padding-top: 12px;
"
>
<h4
style="
margin: 0 0 8px 0;
font-size: 0.85em;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
"
>
<i class="fas fa-trash"></i>
</button>
Session Timeline
</h4>
<div
id="projectHistoryList"
class="history-list custom-scrollbar"
style="max-height: 180px; overflow-y: auto"
></div>

<div style="margin-top: 10px">
<button
id="btnReplayProject"
class="btn btn-sm btn-primary"
style="
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
"
onclick="replayProjectHistory()"
>
<i class="fas fa-play"></i> Replay Steps
</button>
</div>
</div>
</div>

<div class="control-group gray-box">
<h3>Anomaly Scanner</h3>
<p class="section-description">
Expand Down
46 changes: 33 additions & 13 deletions src/dataprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Alert } from './alert.js';
import { messenger } from './bus.js';
import { projectManager } from './projectmanager.js';
import { dbManager } from './dbmanager.js';

/**
* DataProcessor Module
Expand Down Expand Up @@ -40,10 +41,9 @@
Config.ANOMALY_TEMPLATES = providedTemplates;
} catch (error) {
console.error('Config Loader:', error);
// Fallback to safe state
try {
Config.ANOMALY_TEMPLATES = {};
} catch (e) {

Check warning on line 46 in src/dataprocessor.js

View workflow job for this annotation

GitHub Actions / validate_and_build

'e' is defined but never used

Check warning on line 46 in src/dataprocessor.js

View workflow job for this annotation

GitHub Actions / validate_and_build

'e' is defined but never used
/* ignore */
}
}
Expand All @@ -69,17 +69,16 @@
files.forEach((file) => {
const reader = new FileReader();

reader.onload = (e) => {
reader.onload = async (e) => {
try {
let rawData;
if (file.name.endsWith('.csv')) {
const parsedCSV = this.#parseCSV(e.target.result);
// Normalize "Wide" CSVs (exported from app) to "Long" format
rawData = this.#normalizeWideCSV(parsedCSV);
} else {
rawData = JSON.parse(e.target.result);
}
this.#process(rawData, file.name);
await this.#process(rawData, file.name);
} catch (err) {
const msg = `Error parsing ${file.name}: ${err.message}`;
console.error(msg);
Expand All @@ -100,8 +99,8 @@
* @param {Array} data - Array of {s, t, v} points
* @param {string} fileName - Source file identifier
*/
process(data, fileName) {
const result = this.#process(data, fileName);
async process(data, fileName) {
const result = await this.#process(data, fileName);
this.#finalizeBatchLoad();
return result;
}
Expand All @@ -110,17 +109,15 @@
* Processes raw telemetry array into a structured log entry.
* @private
*/
#process(data, fileName) {
async #process(data, fileName) {
try {
if (!Array.isArray(data)) throw new Error('Input data must be an array');

let telemetryPoints = data;
let fileMetadata = {};

// Check if the first element is a metadata block
if (data.length > 0 && data[0].metadata) {
fileMetadata = data[0].metadata;
// The rest of the array is the actual telemetry data
telemetryPoints = data.slice(1);
}

Expand All @@ -134,7 +131,7 @@
// Detect schema based on the first actual data point
const schema = this.#detectSchema(telemetryPoints[0]);

// CHANGED: Use flatMap to handle 1-to-many expansion (e.g. Object -> Multiple Signals)
// Use flatMap to handle 1-to-many expansion (e.g. Object -> Multiple Signals)
const processedPoints = telemetryPoints.flatMap((item) =>
this.#applyMappingAndCleaning(item, schema)
);
Expand All @@ -145,10 +142,33 @@
result.metadata = fileMetadata;
result.size = telemetryPoints.length;

AppState.files.push(result);
// --- CHANGED: Check for duplicates in Library before saving ---
const allLibraryFiles = await dbManager.getAllFiles();
const existingFile = allLibraryFiles.find(
(f) => f.name === fileName && f.size === result.size
);

if (existingFile) {
console.log(
`File '${fileName}' already exists in library (ID: ${existingFile.id}). Skipping DB save.`
);
result.dbId = existingFile.id;
} else {
const dbId = await dbManager.saveTelemetry(result);
result.dbId = dbId;
}

const isAlreadyInSession = AppState.files.some(
(f) => f.dbId === result.dbId
);
if (!isAlreadyInSession) {
AppState.files.push(result);
}

// Register with project manager (it handles its own duplicate checks for resources)
projectManager.registerFile({
name: fileName,
dbId: result.dbId,
size: result.size,
metadata: result.metadata,
});
Expand Down Expand Up @@ -265,15 +285,15 @@

const keys = Object.keys(rows[0]);

// 1. If it already has the standard columns, return as is.
// If it already has the standard columns, return as is.
if (
keys.includes('SensorName') &&
(keys.includes('Time_ms') || keys.includes('time'))
) {
return rows;
}

// 2. Detect Time Column
// Detect Time Column
const timeKey = keys.find((k) => k.toLowerCase().includes('time'));
if (!timeKey) return rows;

Expand Down
Loading
Loading