Add mock mode for web UI development and testing
- Add mock data module with simulated camera search and stream discovery - Enable mock mode via ?mock=true URL parameter - Show MOCK MODE indicator when enabled - Remove statistics cards from discovery screen, keep only progress bar - Mock mode works independently from Go backend for easier UI testing
This commit is contained in:
@@ -207,21 +207,6 @@
|
||||
<p id="progress-text" class="progress-text">Starting scan...</p>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<span class="stat-value" id="stat-tested">0</span>
|
||||
<span class="stat-label">Tested</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-value stat-primary" id="stat-found">0</span>
|
||||
<span class="stat-label">Found</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-value" id="stat-remaining">0</span>
|
||||
<span class="stat-label">Remaining</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="streams-section" class="streams-section hidden">
|
||||
<h3 class="section-title">Found Connections</h3>
|
||||
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { MockCameraSearch } from '../mock/mock-data.js';
|
||||
|
||||
export class CameraSearchAPI {
|
||||
constructor(baseURL = null) {
|
||||
constructor(baseURL = null, useMock = false) {
|
||||
// Use relative URLs since API and UI are on the same port
|
||||
if (!baseURL) {
|
||||
this.baseURL = '';
|
||||
} else {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
this.useMock = useMock;
|
||||
this.mockAPI = useMock ? new MockCameraSearch() : null;
|
||||
}
|
||||
|
||||
async search(query, limit = 10) {
|
||||
// Use mock API if enabled
|
||||
if (this.useMock) {
|
||||
return await this.mockAPI.search(query, limit);
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseURL}api/v1/cameras/search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { MockStreamDiscovery } from '../mock/mock-data.js';
|
||||
|
||||
export class StreamDiscoveryAPI {
|
||||
constructor(baseURL = null) {
|
||||
constructor(baseURL = null, useMock = false) {
|
||||
// Use relative URLs since API and UI are on the same port
|
||||
if (!baseURL) {
|
||||
this.baseURL = '';
|
||||
@@ -7,11 +9,19 @@ export class StreamDiscoveryAPI {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
this.eventSource = null;
|
||||
this.useMock = useMock;
|
||||
this.mockAPI = useMock ? new MockStreamDiscovery() : null;
|
||||
}
|
||||
|
||||
discover(request, callbacks) {
|
||||
this.close();
|
||||
|
||||
// Use mock API if enabled
|
||||
if (this.useMock) {
|
||||
this.mockAPI.discover(request, callbacks);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`${this.baseURL}api/v1/streams/discover`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -91,6 +101,9 @@ export class StreamDiscoveryAPI {
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.useMock && this.mockAPI) {
|
||||
this.mockAPI.close();
|
||||
}
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
|
||||
+32
-2
@@ -8,8 +8,12 @@ import { showToast } from './utils/toast.js';
|
||||
|
||||
class StrixApp {
|
||||
constructor() {
|
||||
this.cameraAPI = new CameraSearchAPI();
|
||||
this.streamAPI = new StreamDiscoveryAPI();
|
||||
// Check if mock mode is enabled via URL parameter (?mock=true)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const useMock = urlParams.get('mock') === 'true';
|
||||
|
||||
this.cameraAPI = new CameraSearchAPI(null, useMock);
|
||||
this.streamAPI = new StreamDiscoveryAPI(null, useMock);
|
||||
|
||||
this.searchForm = new SearchForm();
|
||||
this.carousel = new StreamCarousel();
|
||||
@@ -20,6 +24,7 @@ class StrixApp {
|
||||
this.selectedMainStream = null;
|
||||
this.selectedSubStream = null;
|
||||
this.isSelectingSubStream = false;
|
||||
this.useMock = useMock;
|
||||
|
||||
this.init();
|
||||
}
|
||||
@@ -27,6 +32,31 @@ class StrixApp {
|
||||
init() {
|
||||
this.setupEventListeners();
|
||||
this.showScreen('address');
|
||||
|
||||
// Show mock mode indicator if enabled
|
||||
if (this.useMock) {
|
||||
this.showMockIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
showMockIndicator() {
|
||||
const indicator = document.createElement('div');
|
||||
indicator.id = 'mock-indicator';
|
||||
indicator.style.cssText = `
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: #7E57C2;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
`;
|
||||
indicator.textContent = 'MOCK MODE';
|
||||
document.body.appendChild(indicator);
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
// Mock data for development and testing
|
||||
export const MOCK_CAMERAS = [
|
||||
{ brand: "Hikvision", model: "DS-2CD2143G0-I" },
|
||||
{ brand: "Hikvision", model: "DS-2CD2385G1-I" },
|
||||
{ brand: "Hikvision", model: "DS-2CD2T85G1-I8" },
|
||||
{ brand: "Dahua", model: "IPC-HFW5831E-Z5E" },
|
||||
{ brand: "Dahua", model: "IPC-HDW5831R-ZE" },
|
||||
{ brand: "Axis", model: "M3046-V" },
|
||||
{ brand: "Axis", model: "P3245-LVE" },
|
||||
{ brand: "Uniview", model: "IPC2324LB-ADZK-G" },
|
||||
{ brand: "Reolink", model: "RLC-810A" },
|
||||
{ brand: "TP-Link", model: "VIGI C540V" }
|
||||
];
|
||||
|
||||
export const MOCK_STREAMS = [
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100:554/stream1",
|
||||
type: "FFMPEG",
|
||||
resolution: "1920x1080",
|
||||
codec: "h264",
|
||||
fps: 25,
|
||||
bitrate: 4096,
|
||||
audio: true
|
||||
},
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100:554/stream2",
|
||||
type: "FFMPEG",
|
||||
resolution: "640x360",
|
||||
codec: "h264",
|
||||
fps: 15,
|
||||
bitrate: 512,
|
||||
audio: true
|
||||
},
|
||||
{
|
||||
url: "http://admin:password@192.168.1.100:80/onvif/device_service",
|
||||
type: "ONVIF",
|
||||
resolution: "1920x1080",
|
||||
codec: "h264",
|
||||
fps: 25,
|
||||
bitrate: 4096,
|
||||
audio: false
|
||||
},
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100/live/main",
|
||||
type: "FFMPEG",
|
||||
resolution: "2560x1440",
|
||||
codec: "h265",
|
||||
fps: 30,
|
||||
bitrate: 6144,
|
||||
audio: true
|
||||
},
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100/live/sub",
|
||||
type: "FFMPEG",
|
||||
resolution: "704x576",
|
||||
codec: "h264",
|
||||
fps: 15,
|
||||
bitrate: 768,
|
||||
audio: false
|
||||
},
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100:554/ch01/0",
|
||||
type: "FFMPEG",
|
||||
resolution: "3840x2160",
|
||||
codec: "h265",
|
||||
fps: 25,
|
||||
bitrate: 8192,
|
||||
audio: true
|
||||
},
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100:554/ch01/1",
|
||||
type: "FFMPEG",
|
||||
resolution: "1280x720",
|
||||
codec: "h264",
|
||||
fps: 20,
|
||||
bitrate: 2048,
|
||||
audio: false
|
||||
},
|
||||
{
|
||||
url: "http://admin:password@192.168.1.100:8080/video.mjpeg",
|
||||
type: "MJPEG",
|
||||
resolution: "1920x1080",
|
||||
codec: "mjpeg",
|
||||
fps: 10,
|
||||
bitrate: 3072,
|
||||
audio: false
|
||||
},
|
||||
{
|
||||
url: "rtsp://admin:password@192.168.1.100/h264_stream",
|
||||
type: "FFMPEG",
|
||||
resolution: "1920x1080",
|
||||
codec: "h264",
|
||||
fps: 30,
|
||||
bitrate: 4096,
|
||||
audio: true
|
||||
},
|
||||
{
|
||||
url: "http://admin:password@192.168.1.100:8081/stream.m3u8",
|
||||
type: "HLS",
|
||||
resolution: "1920x1080",
|
||||
codec: "h264",
|
||||
fps: 25,
|
||||
bitrate: 4096,
|
||||
audio: true
|
||||
}
|
||||
];
|
||||
|
||||
// Mock Camera Search API
|
||||
export class MockCameraSearch {
|
||||
async search(query, limit = 10) {
|
||||
// Simulate network delay
|
||||
await this.delay(100);
|
||||
|
||||
const results = MOCK_CAMERAS.filter(camera => {
|
||||
const searchStr = `${camera.brand} ${camera.model}`.toLowerCase();
|
||||
return searchStr.includes(query.toLowerCase());
|
||||
});
|
||||
|
||||
return {
|
||||
cameras: results.slice(0, limit),
|
||||
total: results.length
|
||||
};
|
||||
}
|
||||
|
||||
delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Mock Stream Discovery API
|
||||
export class MockStreamDiscovery {
|
||||
constructor() {
|
||||
this.isRunning = false;
|
||||
this.timeoutId = null;
|
||||
}
|
||||
|
||||
discover(request, callbacks) {
|
||||
this.isRunning = true;
|
||||
let tested = 0;
|
||||
const totalToTest = 516;
|
||||
const foundStreams = [...MOCK_STREAMS];
|
||||
|
||||
// Initial progress
|
||||
callbacks.onProgress({
|
||||
tested: 0,
|
||||
found: 0,
|
||||
remaining: totalToTest
|
||||
});
|
||||
|
||||
// Simulate progressive testing
|
||||
const progressInterval = setInterval(() => {
|
||||
if (!this.isRunning) {
|
||||
clearInterval(progressInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
tested += Math.floor(Math.random() * 30) + 20;
|
||||
if (tested > totalToTest) tested = totalToTest;
|
||||
|
||||
callbacks.onProgress({
|
||||
tested: tested,
|
||||
found: foundStreams.length,
|
||||
remaining: totalToTest - tested
|
||||
});
|
||||
|
||||
if (tested >= totalToTest) {
|
||||
clearInterval(progressInterval);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
// Send found streams progressively
|
||||
let streamIndex = 0;
|
||||
const streamInterval = setInterval(() => {
|
||||
if (!this.isRunning) {
|
||||
clearInterval(streamInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
if (streamIndex < foundStreams.length) {
|
||||
callbacks.onStreamFound({
|
||||
stream: foundStreams[streamIndex]
|
||||
});
|
||||
streamIndex++;
|
||||
} else {
|
||||
clearInterval(streamInterval);
|
||||
}
|
||||
}, 800);
|
||||
|
||||
// Complete after ~7.7 seconds
|
||||
this.timeoutId = setTimeout(() => {
|
||||
if (!this.isRunning) return;
|
||||
|
||||
callbacks.onComplete({
|
||||
total_found: foundStreams.length,
|
||||
duration: 7.7
|
||||
});
|
||||
|
||||
this.isRunning = false;
|
||||
}, 7700);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.isRunning = false;
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user