Skip to content

Commit 3b7259e

Browse files
authored
Feat/add persitence layer
1 parent d908d2e commit 3b7259e

11 files changed

Lines changed: 1340 additions & 362 deletions

index.html

Lines changed: 105 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -297,43 +297,117 @@ <h3 class="group-header">Cloud Files</h3>
297297
<div id="fileInfo" class="status-text-subtle"></div>
298298
</div>
299299

300-
<div id="projectHistoryContainer" class="control-group gray-box">
301-
<div class="group-header-row">
302-
<h3 id="projectNameDisplay">Project History</h3>
303-
<button
304-
class="btn-icon btn-rename-project"
305-
onclick="
306-
event.stopPropagation();
307-
editProjectName();
308-
"
309-
title="Rename Project"
310-
>
311-
<i class="fas fa-pen"></i>
312-
</button>
300+
<div
301+
id="projectUnifiedPanel"
302+
class="control-group gray-box"
303+
style="
304+
display: flex;
305+
flex-direction: column;
306+
gap: 16px;
307+
padding: 12px;
308+
"
309+
>
310+
<div
311+
class="group-header-row"
312+
style="
313+
display: flex;
314+
justify-content: space-between;
315+
align-items: flex-end;
316+
border-bottom: 1px solid var(--border-color);
317+
padding-bottom: 8px;
318+
cursor: pointer;
319+
"
320+
>
321+
<div>
322+
<span
323+
style="
324+
display: block;
325+
font-size: 0.7em;
326+
color: var(--text-muted);
327+
text-transform: uppercase;
328+
letter-spacing: 0.5px;
329+
margin-bottom: 2px;
330+
"
331+
>Active Session</span
332+
>
333+
<h3
334+
id="projectNameDisplay"
335+
style="
336+
margin: 0;
337+
font-size: 1.1em;
338+
line-height: 1.2;
339+
white-space: nowrap;
340+
overflow: hidden;
341+
text-overflow: ellipsis;
342+
max-width: 180px;
343+
"
344+
>
345+
Default Project
346+
</h3>
347+
</div>
348+
<div style="display: flex; gap: 6px">
349+
<button
350+
class="btn-icon"
351+
onclick="editProjectName()"
352+
title="Rename Project"
353+
style="
354+
width: 24px;
355+
height: 24px;
356+
padding: 0;
357+
display: flex;
358+
align-items: center;
359+
justify-content: center;
360+
"
361+
>
362+
<i class="fas fa-pen" style="font-size: 0.8em"></i>
363+
</button>
364+
</div>
313365
</div>
314366

315-
<p class="section-description">Timeline of analysis steps.</p>
316-
317-
<div id="projectHistoryList" class="history-list"></div>
367+
<div id="librarySlot"></div>
318368

319-
<div class="flex-row-gap-5">
320-
<button
321-
id="btnReplayProject"
322-
class="btn btn-sm btn-primary flex-1"
323-
onclick="replayProjectHistory()"
324-
>
325-
<i class="fas fa-play"></i> Replay All
326-
</button>
327-
<button
328-
class="btn btn-sm"
329-
onclick="resetProject()"
330-
title="Clear History"
369+
<div
370+
id="historySlot"
371+
style="
372+
border-top: 1px solid var(--border-color);
373+
padding-top: 12px;
374+
"
375+
>
376+
<h4
377+
style="
378+
margin: 0 0 8px 0;
379+
font-size: 0.85em;
380+
text-transform: uppercase;
381+
letter-spacing: 0.5px;
382+
color: var(--text-muted);
383+
"
331384
>
332-
<i class="fas fa-trash"></i>
333-
</button>
385+
Session Timeline
386+
</h4>
387+
<div
388+
id="projectHistoryList"
389+
class="history-list custom-scrollbar"
390+
style="max-height: 180px; overflow-y: auto"
391+
></div>
392+
393+
<div style="margin-top: 10px">
394+
<button
395+
id="btnReplayProject"
396+
class="btn btn-sm btn-primary"
397+
style="
398+
width: 100%;
399+
display: flex;
400+
align-items: center;
401+
justify-content: center;
402+
gap: 8px;
403+
"
404+
onclick="replayProjectHistory()"
405+
>
406+
<i class="fas fa-play"></i> Replay Steps
407+
</button>
408+
</div>
334409
</div>
335410
</div>
336-
337411
<div class="control-group gray-box">
338412
<h3>Anomaly Scanner</h3>
339413
<p class="section-description">

src/dataprocessor.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Config, AppState, DOM } from './config.js';
33
import { Alert } from './alert.js';
44
import { messenger } from './bus.js';
55
import { projectManager } from './projectmanager.js';
6+
import { dbManager } from './dbmanager.js';
67

78
/**
89
* DataProcessor Module
@@ -40,7 +41,6 @@ class DataProcessor {
4041
Config.ANOMALY_TEMPLATES = providedTemplates;
4142
} catch (error) {
4243
console.error('Config Loader:', error);
43-
// Fallback to safe state
4444
try {
4545
Config.ANOMALY_TEMPLATES = {};
4646
} catch (e) {
@@ -69,17 +69,16 @@ class DataProcessor {
6969
files.forEach((file) => {
7070
const reader = new FileReader();
7171

72-
reader.onload = (e) => {
72+
reader.onload = async (e) => {
7373
try {
7474
let rawData;
7575
if (file.name.endsWith('.csv')) {
7676
const parsedCSV = this.#parseCSV(e.target.result);
77-
// Normalize "Wide" CSVs (exported from app) to "Long" format
7877
rawData = this.#normalizeWideCSV(parsedCSV);
7978
} else {
8079
rawData = JSON.parse(e.target.result);
8180
}
82-
this.#process(rawData, file.name);
81+
await this.#process(rawData, file.name);
8382
} catch (err) {
8483
const msg = `Error parsing ${file.name}: ${err.message}`;
8584
console.error(msg);
@@ -100,8 +99,8 @@ class DataProcessor {
10099
* @param {Array} data - Array of {s, t, v} points
101100
* @param {string} fileName - Source file identifier
102101
*/
103-
process(data, fileName) {
104-
const result = this.#process(data, fileName);
102+
async process(data, fileName) {
103+
const result = await this.#process(data, fileName);
105104
this.#finalizeBatchLoad();
106105
return result;
107106
}
@@ -110,17 +109,15 @@ class DataProcessor {
110109
* Processes raw telemetry array into a structured log entry.
111110
* @private
112111
*/
113-
#process(data, fileName) {
112+
async #process(data, fileName) {
114113
try {
115114
if (!Array.isArray(data)) throw new Error('Input data must be an array');
116115

117116
let telemetryPoints = data;
118117
let fileMetadata = {};
119118

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

@@ -134,7 +131,7 @@ class DataProcessor {
134131
// Detect schema based on the first actual data point
135132
const schema = this.#detectSchema(telemetryPoints[0]);
136133

137-
// CHANGED: Use flatMap to handle 1-to-many expansion (e.g. Object -> Multiple Signals)
134+
// Use flatMap to handle 1-to-many expansion (e.g. Object -> Multiple Signals)
138135
const processedPoints = telemetryPoints.flatMap((item) =>
139136
this.#applyMappingAndCleaning(item, schema)
140137
);
@@ -145,10 +142,33 @@ class DataProcessor {
145142
result.metadata = fileMetadata;
146143
result.size = telemetryPoints.length;
147144

148-
AppState.files.push(result);
145+
// --- CHANGED: Check for duplicates in Library before saving ---
146+
const allLibraryFiles = await dbManager.getAllFiles();
147+
const existingFile = allLibraryFiles.find(
148+
(f) => f.name === fileName && f.size === result.size
149+
);
150+
151+
if (existingFile) {
152+
console.log(
153+
`File '${fileName}' already exists in library (ID: ${existingFile.id}). Skipping DB save.`
154+
);
155+
result.dbId = existingFile.id;
156+
} else {
157+
const dbId = await dbManager.saveTelemetry(result);
158+
result.dbId = dbId;
159+
}
160+
161+
const isAlreadyInSession = AppState.files.some(
162+
(f) => f.dbId === result.dbId
163+
);
164+
if (!isAlreadyInSession) {
165+
AppState.files.push(result);
166+
}
149167

168+
// Register with project manager (it handles its own duplicate checks for resources)
150169
projectManager.registerFile({
151170
name: fileName,
171+
dbId: result.dbId,
152172
size: result.size,
153173
metadata: result.metadata,
154174
});
@@ -265,15 +285,15 @@ class DataProcessor {
265285

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

268-
// 1. If it already has the standard columns, return as is.
288+
// If it already has the standard columns, return as is.
269289
if (
270290
keys.includes('SensorName') &&
271291
(keys.includes('Time_ms') || keys.includes('time'))
272292
) {
273293
return rows;
274294
}
275295

276-
// 2. Detect Time Column
296+
// Detect Time Column
277297
const timeKey = keys.find((k) => k.toLowerCase().includes('time'));
278298
if (!timeKey) return rows;
279299

0 commit comments

Comments
 (0)