Fix SSE stream discovery and add ESLint for WebUI

JavaScript fixes:
- Fix Invalid URL error in stream-discovery.js by adding window.location.origin as base URL
- Fix SSE race condition: ensure all stream_found events sent before complete event
- Comment unused eventType variable in stream-discovery.js
- Comment unused FrigateGenerator import in config-panel.js
- Fix quote style (double to single quotes)

Go fixes:
- Add sync.WaitGroup for result collector to prevent premature SSE closure
- Add logging for stream_found events
- Fix ERR_INCOMPLETE_CHUNKED_ENCODING by waiting for all streams to be sent

ESLint setup:
- Add ESLint v8.57.1 with .eslintrc.cjs config for ES2022 modules
- Add npm scripts for lint and lint:fix
- Update .gitignore for node_modules and package-lock.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
eduard256
2025-11-12 11:45:46 +03:00
parent 86a8fb36d5
commit d12d732671
7 changed files with 61 additions and 5 deletions
+5 -1
View File
@@ -43,4 +43,8 @@ temp/
*_output.txt
# Configuration (user-specific)
strix.yaml
strix.yaml
# Node.js / NPM
node_modules/
package-lock.json
+8
View File
@@ -434,10 +434,15 @@ func (s *Scanner) testStreamsConcurrently(ctx context.Context, streams []models.
}()
// Start result collector
var collectorWg sync.WaitGroup
collectorWg.Add(1)
go func() {
defer collectorWg.Done()
for stream := range streamsChan {
result.Streams = append(result.Streams, stream)
s.logger.Info("sending stream_found event", "url", stream.URL, "type", stream.Type)
// Send to SSE
_ = streamWriter.SendJSON("stream_found", map[string]interface{}{
"stream": stream,
@@ -522,6 +527,9 @@ TestLoop:
wg.Wait()
close(streamsChan)
// Wait for result collector to finish processing all streams
collectorWg.Wait()
// Update final counts
result.TotalTested = int(atomic.LoadInt32(&tested))
result.TotalFound = int(atomic.LoadInt32(&found))
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: 'eslint:recommended',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
EventSource: 'readonly',
},
rules: {
'no-unused-vars': 'warn',
'no-undef': 'error',
'no-console': 'off', // Allow console for debugging
'semi': ['error', 'always'],
'quotes': ['warn', 'single', { avoidEscape: true }],
'no-var': 'error',
'prefer-const': 'warn',
'eqeqeq': ['error', 'always'],
'no-unreachable': 'error',
},
};
+18
View File
@@ -0,0 +1,18 @@
{
"name": "webui",
"version": "1.0.0",
"type": "module",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint web/js/**/*.js",
"lint:fix": "eslint web/js/**/*.js --fix"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"eslint": "^8.57.1"
}
}
+3 -2
View File
@@ -12,7 +12,7 @@ export class StreamDiscoveryAPI {
discover(request, callbacks) {
this.close();
const url = new URL(`${this.baseURL}/api/v1/streams/discover`);
const url = new URL(`${this.baseURL}/api/v1/streams/discover`, window.location.origin);
fetch(url, {
method: 'POST',
@@ -39,7 +39,8 @@ export class StreamDiscoveryAPI {
for (const line of lines) {
if (line.startsWith('event:')) {
const eventType = line.substring(6).trim();
// Parse event type (not currently used, but available for future features)
// const eventType = line.substring(6).trim();
continue;
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { Go2RTCGenerator } from '../config-generators/go2rtc/index.js';
import { FrigateGenerator } from '../config-generators/frigate/index.js';
// import { FrigateGenerator } from '../config-generators/frigate/index.js'; // Reserved for future use
export class ConfigPanel {
constructor() {
+1 -1
View File
@@ -45,7 +45,7 @@ export class StreamCarousel {
<div class="stream-url">${this.truncateURL(stream.url)}</div>
${stream.resolution ? `<div class="stream-meta">Resolution: ${stream.resolution}</div>` : ''}
${stream.codec ? `<div class="stream-meta">Codec: ${stream.codec}${stream.fps ? `${stream.fps} fps` : ''}${stream.bitrate ? `${Math.round(stream.bitrate / 1000)} Kbps` : ''}</div>` : ''}
${stream.has_audio ? `<div class="stream-meta">Audio: Yes</div>` : ''}
${stream.has_audio ? '<div class="stream-meta">Audio: Yes</div>' : ''}
<div class="stream-actions">
<button class="btn btn-primary btn-use" data-index="${index}">Use Stream</button>
</div>