|
|
@@ -3,71 +3,132 @@
|
|
|
<h1>Vault & History</h1>
|
|
|
<p>View previous detections and industrial reports.</p>
|
|
|
|
|
|
- <div class="vault-tabs">
|
|
|
- <button [class.active]="viewMode === 'local'" (click)="switchTab('local')" class="tab-btn">
|
|
|
- <span class="icon">💻</span> Browser Cache
|
|
|
- </button>
|
|
|
- <button [class.active]="viewMode === 'remote'" (click)="switchTab('remote')" class="tab-btn">
|
|
|
- <span class="icon">☁️</span> Industrial Cloud (API)
|
|
|
- </button>
|
|
|
+ <div class="vault-tabs-row">
|
|
|
+ <div class="vault-tabs">
|
|
|
+ <button class="tab-btn" [class.active]="viewMode === 'local'" (click)="switchTab('local')">
|
|
|
+ <span class="icon">💻</span> Browser Cache
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="tab-btn"
|
|
|
+ [class.active]="viewMode === 'remote'"
|
|
|
+ [class.tab-disabled]="surveillance.nestStatus() === 'OFFLINE'"
|
|
|
+ [disabled]="surveillance.nestStatus() === 'OFFLINE'"
|
|
|
+ (click)="switchTab('remote')"
|
|
|
+ [title]="surveillance.nestStatus() === 'OFFLINE' ? 'NestJS offline — Industrial Cloud unavailable' : ''">
|
|
|
+ <span class="icon">☁️</span> Industrial Cloud (API)
|
|
|
+ <span class="nest-dot"
|
|
|
+ [class.dot-live]="surveillance.nestStatus() === 'ONLINE'"
|
|
|
+ [class.dot-off]="surveillance.nestStatus() === 'OFFLINE'">
|
|
|
+ </span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Clear All button — context-sensitive to active tab -->
|
|
|
+ @if (viewMode === 'local' && localHistoryRecords.length > 0) {
|
|
|
+ <button class="btn-clear-all" (click)="clearLocalHistory($event)">
|
|
|
+ 🗑 Clear All
|
|
|
+ </button>
|
|
|
+ }
|
|
|
+ @if (viewMode === 'remote' && remoteHistoryRecords.length > 0) {
|
|
|
+ <button class="btn-clear-all btn-clear-all--remote" (click)="clearRemoteHistory($event)">
|
|
|
+ 🗑 Clear All
|
|
|
+ </button>
|
|
|
+ }
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div *ngIf="loading" class="loading-state glass-panel">
|
|
|
- <div class="spinner"></div>
|
|
|
- <p>Synchronizing vault data...</p>
|
|
|
+ @if (surveillance.nestStatus() === 'OFFLINE') {
|
|
|
+ <div class="cloud-offline-banner">
|
|
|
+ ⚠ NestJS Offline — Industrial Cloud (API) is unavailable. Browser Cache only.
|
|
|
+ </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
|
|
|
- <div *ngIf="!loading && currentHistory.length === 0" class="empty-state glass-panel">
|
|
|
- <div class="empty-icon">📭</div>
|
|
|
- <p *ngIf="viewMode === 'local'">Your browser cache is empty. Run a local analysis to save records.</p>
|
|
|
- <p *ngIf="viewMode === 'remote'">No industrial records found on the server.</p>
|
|
|
- </div>
|
|
|
+ @if (loading) {
|
|
|
+ <div class="loading-state glass-panel">
|
|
|
+ <div class="spinner"></div>
|
|
|
+ <p>Synchronizing vault data...</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <div *ngIf="!loading && currentHistory.length > 0" class="history-list">
|
|
|
- <div *ngFor="let record of currentHistory" class="history-card glass-panel"
|
|
|
- [class.expanded]="isExpanded(record.timestamp, record.filename, record.archive_id)">
|
|
|
+ @if (!loading && currentHistory.length === 0) {
|
|
|
+ <div class="empty-state glass-panel">
|
|
|
+ <div class="empty-icon">📭</div>
|
|
|
+ @if (viewMode === 'local') {
|
|
|
+ <p>Your browser cache is empty. Run a local analysis to save records.</p>
|
|
|
+ }
|
|
|
+ @if (viewMode === 'remote') {
|
|
|
+ <p>No industrial records found on the server.</p>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
|
|
|
- <div class="card-header" (click)="toggleExpand(record.timestamp, record.filename, record.archive_id)">
|
|
|
- <div class="card-main-info">
|
|
|
- <span class="timestamp">{{ record.timestamp }}</span>
|
|
|
- <span class="filename">{{ record.filename }}</span>
|
|
|
- <span class="engine-badge">{{ record.engine }}</span>
|
|
|
- </div>
|
|
|
- <div class="summary-mini">
|
|
|
- <span *ngFor="let badge of getSummaryBadge(record.industrial_summary || record.summary)" class="badge">
|
|
|
- {{ badge }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="expand-icon">{{ isExpanded(record.timestamp, record.filename, record.archive_id) ? '▾' : '▸' }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ @if (!loading && currentHistory.length > 0) {
|
|
|
+ <div class="history-list">
|
|
|
+ @for (record of currentHistory; track record.archive_id ?? (record.timestamp + record.filename)) {
|
|
|
+ <div class="history-card glass-panel"
|
|
|
+ [class.expanded]="isExpanded(record.timestamp, record.filename, record.archive_id)">
|
|
|
|
|
|
- <div *ngIf="isExpanded(record.timestamp, record.filename, record.archive_id)" class="card-details">
|
|
|
- <hr>
|
|
|
- <div class="details-grid">
|
|
|
- <div class="preview-side">
|
|
|
- <div class="image-wrapper" *ngIf="record.imageData">
|
|
|
- <img [src]="record.imageData" (error)="record.imageData = null">
|
|
|
- <div *ngFor="let det of record.detections" class="detection-box"
|
|
|
- [ngStyle]="getBoxStyles(det.box, record)">
|
|
|
- </div>
|
|
|
+ <div class="card-header" (click)="toggleExpand(record.timestamp, record.filename, record.archive_id)">
|
|
|
+ <div class="card-main-info">
|
|
|
+ <span class="timestamp">{{ record.timestamp }}</span>
|
|
|
+ <span class="filename">{{ record.filename }}</span>
|
|
|
+ <span class="engine-badge">{{ record.engine }}</span>
|
|
|
</div>
|
|
|
- <div *ngIf="!record.imageData" class="no-image-preview">
|
|
|
- <p>Cloud Archive: Metadata Only</p>
|
|
|
+ <div class="summary-mini">
|
|
|
+ @for (badge of getSummaryBadge(record.industrial_summary || record.summary); track badge) {
|
|
|
+ <span class="badge">{{ badge }}</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="card-actions">
|
|
|
+ <span class="expand-icon">
|
|
|
+ {{ isExpanded(record.timestamp, record.filename, record.archive_id) ? '▾' : '▸' }}
|
|
|
+ </span>
|
|
|
+ @if (viewMode === 'local') {
|
|
|
+ <button class="btn-delete-item" title="Delete record"
|
|
|
+ (click)="deleteLocalRecord(record, $event)">✕</button>
|
|
|
+ }
|
|
|
+ @if (viewMode === 'remote') {
|
|
|
+ <button class="btn-delete-item" title="Delete record"
|
|
|
+ (click)="deleteRemoteRecord(record, $event)">✕</button>
|
|
|
+ }
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="data-side">
|
|
|
- <h3>Industrial Metrics</h3>
|
|
|
- <ul>
|
|
|
- <li><strong>Total Bunches:</strong> {{ record.detections.length }}</li>
|
|
|
- <li><strong>Archive ID:</strong> {{ record.archive_id || 'LOCAL_ONLY' }}</li>
|
|
|
- <li><strong>Inference:</strong> {{ record.inference_ms }} ms</li>
|
|
|
- <li><strong>Processing:</strong> {{ (record.processing_ms || 0).toFixed(1) }} ms</li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
+
|
|
|
+ @if (isExpanded(record.timestamp, record.filename, record.archive_id)) {
|
|
|
+ <div class="card-details">
|
|
|
+ <hr>
|
|
|
+ <div class="details-grid">
|
|
|
+ <div class="preview-side">
|
|
|
+ @if (record.imageData) {
|
|
|
+ <div class="image-wrapper">
|
|
|
+ <img [src]="record.imageData" (error)="record.imageData = null">
|
|
|
+ @for (det of record.detections; track $index) {
|
|
|
+ <div class="detection-box" [ngStyle]="getBoxStyles(det.box, record)"></div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ @if (!record.imageData) {
|
|
|
+ <div class="no-image-preview">
|
|
|
+ <p>Cloud Archive: Metadata Only</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div class="data-side">
|
|
|
+ <h3>Industrial Metrics</h3>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Total Bunches:</strong> {{ record.detections.length }}</li>
|
|
|
+ <li><strong>Archive ID:</strong> {{ record.archive_id || 'LOCAL_ONLY' }}</li>
|
|
|
+ <li><strong>Inference:</strong> {{ record.inference_ms }} ms</li>
|
|
|
+ <li><strong>Processing:</strong> {{ (record.processing_ms || 0).toFixed(1) }} ms</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ }
|
|
|
</div>
|
|
|
- </div>
|
|
|
-</div>
|
|
|
+ }
|
|
|
+
|
|
|
+</div>
|