mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Add config editor to webUI (#4608)
* Add raw config endpoint * Add config editor * Add code editor * Add error * Add ability to copy config * Only show the save button when code has been edited * Update errors * Remove debug config from system page * Break out config saving steps to pinpoint where error occurred. * Show correct config errors * Switch to monaco editor * Adjust UI colors and behavior * Get yaml validation working * Set success color
This commit is contained in:
		
							parent
							
								
									97161310a5
								
							
						
					
					
						commit
						7888059c9f
					
				@ -1028,3 +1028,8 @@ class FrigateConfig(FrigateBaseModel):
 | 
				
			|||||||
            config = json.loads(raw_config)
 | 
					            config = json.loads(raw_config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return cls.parse_obj(config)
 | 
					        return cls.parse_obj(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def parse_raw(cls, raw_config):
 | 
				
			||||||
 | 
					        config = load_config_with_no_duplicates(raw_config)
 | 
				
			||||||
 | 
					        return cls.parse_obj(config)
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import json
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import subprocess as sp
 | 
					import subprocess as sp
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
from functools import reduce
 | 
					from functools import reduce
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from urllib.parse import unquote
 | 
					from urllib.parse import unquote
 | 
				
			||||||
@ -27,11 +28,17 @@ from flask import (
 | 
				
			|||||||
from peewee import SqliteDatabase, operator, fn, DoesNotExist
 | 
					from peewee import SqliteDatabase, operator, fn, DoesNotExist
 | 
				
			||||||
from playhouse.shortcuts import model_to_dict
 | 
					from playhouse.shortcuts import model_to_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from frigate.config import FrigateConfig
 | 
				
			||||||
from frigate.const import CLIPS_DIR, RECORD_DIR
 | 
					from frigate.const import CLIPS_DIR, RECORD_DIR
 | 
				
			||||||
from frigate.models import Event, Recordings
 | 
					from frigate.models import Event, Recordings
 | 
				
			||||||
from frigate.object_processing import TrackedObject
 | 
					from frigate.object_processing import TrackedObject
 | 
				
			||||||
from frigate.stats import stats_snapshot
 | 
					from frigate.stats import stats_snapshot
 | 
				
			||||||
from frigate.util import clean_camera_user_pass, ffprobe_stream, vainfo_hwaccel
 | 
					from frigate.util import (
 | 
				
			||||||
 | 
					    clean_camera_user_pass,
 | 
				
			||||||
 | 
					    ffprobe_stream,
 | 
				
			||||||
 | 
					    restart_frigate,
 | 
				
			||||||
 | 
					    vainfo_hwaccel,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from frigate.storage import StorageMaintainer
 | 
					from frigate.storage import StorageMaintainer
 | 
				
			||||||
from frigate.version import VERSION
 | 
					from frigate.version import VERSION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -599,7 +606,81 @@ def config():
 | 
				
			|||||||
    return jsonify(config)
 | 
					    return jsonify(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@bp.route("/config/schema")
 | 
					@bp.route("/config/raw")
 | 
				
			||||||
 | 
					def config_raw():
 | 
				
			||||||
 | 
					    config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check if we can use .yaml instead of .yml
 | 
				
			||||||
 | 
					    config_file_yaml = config_file.replace(".yml", ".yaml")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if os.path.isfile(config_file_yaml):
 | 
				
			||||||
 | 
					        config_file = config_file_yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not os.path.isfile(config_file):
 | 
				
			||||||
 | 
					        return "Could not find file", 410
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(config_file, "r") as f:
 | 
				
			||||||
 | 
					        raw_config = f.read()
 | 
				
			||||||
 | 
					        f.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return raw_config, 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@bp.route("/config/save", methods=["POST"])
 | 
				
			||||||
 | 
					def config_save():
 | 
				
			||||||
 | 
					    new_config = request.get_data().decode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not new_config:
 | 
				
			||||||
 | 
					        return "Config with body param is required", 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Validate the config schema
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        new_yaml = FrigateConfig.parse_raw(new_config)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        return make_response(
 | 
				
			||||||
 | 
					            jsonify(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "success": False,
 | 
				
			||||||
 | 
					                    "message": f"\nConfig Error:\n\n{str(traceback.format_exc())}",
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Save the config to file
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check if we can use .yaml instead of .yml
 | 
				
			||||||
 | 
					        config_file_yaml = config_file.replace(".yml", ".yaml")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if os.path.isfile(config_file_yaml):
 | 
				
			||||||
 | 
					            config_file = config_file_yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with open(config_file, "w") as f:
 | 
				
			||||||
 | 
					            f.write(new_config)
 | 
				
			||||||
 | 
					            f.close()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        return make_response(
 | 
				
			||||||
 | 
					            jsonify(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "success": False,
 | 
				
			||||||
 | 
					                    "message": f"Could not write config file, be sure that frigate has write permission on the config file.",
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        restart_frigate()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logging.error(f"Error restarting frigate: {e}")
 | 
				
			||||||
 | 
					        return "Config successfully saved, unable to restart frigate", 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return "Config successfully saved, restarting...", 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@bp.route("/config/schema.json")
 | 
				
			||||||
def config_schema():
 | 
					def config_schema():
 | 
				
			||||||
    return current_app.response_class(
 | 
					    return current_app.response_class(
 | 
				
			||||||
        current_app.frigate_config.schema_json(), mimetype="application/json"
 | 
					        current_app.frigate_config.schema_json(), mimetype="application/json"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										176
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										176
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -13,6 +13,7 @@
 | 
				
			|||||||
        "date-fns": "^2.29.3",
 | 
					        "date-fns": "^2.29.3",
 | 
				
			||||||
        "idb-keyval": "^6.2.0",
 | 
					        "idb-keyval": "^6.2.0",
 | 
				
			||||||
        "immer": "^9.0.16",
 | 
					        "immer": "^9.0.16",
 | 
				
			||||||
 | 
					        "monaco-yaml": "^4.0.2",
 | 
				
			||||||
        "preact": "^10.11.3",
 | 
					        "preact": "^10.11.3",
 | 
				
			||||||
        "preact-async-route": "^2.2.1",
 | 
					        "preact-async-route": "^2.2.1",
 | 
				
			||||||
        "preact-router": "^4.1.0",
 | 
					        "preact-router": "^4.1.0",
 | 
				
			||||||
@ -21,7 +22,8 @@
 | 
				
			|||||||
        "swr": "^1.3.0",
 | 
					        "swr": "^1.3.0",
 | 
				
			||||||
        "video.js": "^7.20.3",
 | 
					        "video.js": "^7.20.3",
 | 
				
			||||||
        "videojs-playlist": "^5.0.0",
 | 
					        "videojs-playlist": "^5.0.0",
 | 
				
			||||||
        "videojs-seek-buttons": "^3.0.1"
 | 
					        "videojs-seek-buttons": "^3.0.1",
 | 
				
			||||||
 | 
					        "vite-plugin-monaco-editor": "^1.1.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
        "@preact/preset-vite": "^2.4.0",
 | 
					        "@preact/preset-vite": "^2.4.0",
 | 
				
			||||||
@ -1361,8 +1363,7 @@
 | 
				
			|||||||
    "node_modules/@types/json-schema": {
 | 
					    "node_modules/@types/json-schema": {
 | 
				
			||||||
      "version": "7.0.11",
 | 
					      "version": "7.0.11",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
 | 
				
			||||||
      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
 | 
					      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/ms": {
 | 
					    "node_modules/@types/ms": {
 | 
				
			||||||
      "version": "0.7.31",
 | 
					      "version": "0.7.31",
 | 
				
			||||||
@ -6257,6 +6258,11 @@
 | 
				
			|||||||
        "node": ">=6"
 | 
					        "node": ">=6"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/jsonc-parser": {
 | 
				
			||||||
 | 
					      "version": "3.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/jsx-ast-utils": {
 | 
					    "node_modules/jsx-ast-utils": {
 | 
				
			||||||
      "version": "3.3.3",
 | 
					      "version": "3.3.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
 | 
				
			||||||
@ -6598,6 +6604,62 @@
 | 
				
			|||||||
        "url": "https://github.com/sponsors/ljharb"
 | 
					        "url": "https://github.com/sponsors/ljharb"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/monaco-editor": {
 | 
				
			||||||
 | 
					      "version": "0.34.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==",
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/monaco-marker-data-provider": {
 | 
				
			||||||
 | 
					      "version": "1.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==",
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "url": "https://github.com/sponsors/remcohaszing"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "monaco-editor": ">=0.30.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/monaco-worker-manager": {
 | 
				
			||||||
 | 
					      "version": "2.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==",
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "monaco-editor": ">=0.30.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/monaco-yaml": {
 | 
				
			||||||
 | 
					      "version": "4.0.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-4.0.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Wxn6CblkQDLOUusfi0eZ3qZhkuKYIrK7fXlkJOOG+W18zgKePbuZW0XNWpczlxDC27D753dB18pMnx4U7MZ3yg==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@types/json-schema": "^7.0.0",
 | 
				
			||||||
 | 
					        "jsonc-parser": "^3.0.0",
 | 
				
			||||||
 | 
					        "monaco-marker-data-provider": "^1.0.0",
 | 
				
			||||||
 | 
					        "monaco-worker-manager": "^2.0.0",
 | 
				
			||||||
 | 
					        "path-browserify": "^1.0.0",
 | 
				
			||||||
 | 
					        "prettier": "^2.0.0",
 | 
				
			||||||
 | 
					        "vscode-languageserver-textdocument": "^1.0.0",
 | 
				
			||||||
 | 
					        "vscode-languageserver-types": "^3.0.0",
 | 
				
			||||||
 | 
					        "vscode-uri": "^3.0.0",
 | 
				
			||||||
 | 
					        "yaml": "^2.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "url": "https://github.com/sponsors/remcohaszing"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "monaco-editor": ">=0.30"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/monaco-yaml/node_modules/yaml": {
 | 
				
			||||||
 | 
					      "version": "2.1.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 14"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/mpd-parser": {
 | 
					    "node_modules/mpd-parser": {
 | 
				
			||||||
      "version": "0.21.1",
 | 
					      "version": "0.21.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
 | 
				
			||||||
@ -7181,6 +7243,11 @@
 | 
				
			|||||||
        "url": "https://github.com/inikulin/parse5?sponsor=1"
 | 
					        "url": "https://github.com/inikulin/parse5?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/path-browserify": {
 | 
				
			||||||
 | 
					      "version": "1.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/path-exists": {
 | 
					    "node_modules/path-exists": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 | 
				
			||||||
@ -7438,7 +7505,6 @@
 | 
				
			|||||||
      "version": "2.8.0",
 | 
					      "version": "2.8.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
 | 
					      "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
        "prettier": "bin-prettier.js"
 | 
					        "prettier": "bin-prettier.js"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -8780,6 +8846,14 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vite-plugin-monaco-editor": {
 | 
				
			||||||
 | 
					      "version": "1.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "monaco-editor": ">=0.33.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/vitest": {
 | 
					    "node_modules/vitest": {
 | 
				
			||||||
      "version": "0.25.3",
 | 
					      "version": "0.25.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.3.tgz",
 | 
				
			||||||
@ -8835,6 +8909,21 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vscode-languageserver-textdocument": {
 | 
				
			||||||
 | 
					      "version": "1.0.7",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.7.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vscode-languageserver-types": {
 | 
				
			||||||
 | 
					      "version": "3.17.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/vscode-uri": {
 | 
				
			||||||
 | 
					      "version": "3.0.6",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/w3c-xmlserializer": {
 | 
					    "node_modules/w3c-xmlserializer": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
 | 
				
			||||||
@ -10160,8 +10249,7 @@
 | 
				
			|||||||
    "@types/json-schema": {
 | 
					    "@types/json-schema": {
 | 
				
			||||||
      "version": "7.0.11",
 | 
					      "version": "7.0.11",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
 | 
				
			||||||
      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
 | 
					      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@types/ms": {
 | 
					    "@types/ms": {
 | 
				
			||||||
      "version": "0.7.31",
 | 
					      "version": "0.7.31",
 | 
				
			||||||
@ -13622,6 +13710,11 @@
 | 
				
			|||||||
      "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
 | 
					      "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "jsonc-parser": {
 | 
				
			||||||
 | 
					      "version": "3.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "jsx-ast-utils": {
 | 
					    "jsx-ast-utils": {
 | 
				
			||||||
      "version": "3.3.3",
 | 
					      "version": "3.3.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
 | 
				
			||||||
@ -13875,6 +13968,48 @@
 | 
				
			|||||||
      "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
 | 
					      "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "monaco-editor": {
 | 
				
			||||||
 | 
					      "version": "0.34.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==",
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "monaco-marker-data-provider": {
 | 
				
			||||||
 | 
					      "version": "1.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==",
 | 
				
			||||||
 | 
					      "requires": {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "monaco-worker-manager": {
 | 
				
			||||||
 | 
					      "version": "2.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==",
 | 
				
			||||||
 | 
					      "requires": {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "monaco-yaml": {
 | 
				
			||||||
 | 
					      "version": "4.0.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-4.0.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Wxn6CblkQDLOUusfi0eZ3qZhkuKYIrK7fXlkJOOG+W18zgKePbuZW0XNWpczlxDC27D753dB18pMnx4U7MZ3yg==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@types/json-schema": "^7.0.0",
 | 
				
			||||||
 | 
					        "jsonc-parser": "^3.0.0",
 | 
				
			||||||
 | 
					        "monaco-marker-data-provider": "^1.0.0",
 | 
				
			||||||
 | 
					        "monaco-worker-manager": "^2.0.0",
 | 
				
			||||||
 | 
					        "path-browserify": "^1.0.0",
 | 
				
			||||||
 | 
					        "prettier": "^2.0.0",
 | 
				
			||||||
 | 
					        "vscode-languageserver-textdocument": "^1.0.0",
 | 
				
			||||||
 | 
					        "vscode-languageserver-types": "^3.0.0",
 | 
				
			||||||
 | 
					        "vscode-uri": "^3.0.0",
 | 
				
			||||||
 | 
					        "yaml": "^2.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "yaml": {
 | 
				
			||||||
 | 
					          "version": "2.1.3",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg=="
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "mpd-parser": {
 | 
					    "mpd-parser": {
 | 
				
			||||||
      "version": "0.21.1",
 | 
					      "version": "0.21.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
 | 
				
			||||||
@ -14287,6 +14422,11 @@
 | 
				
			|||||||
        "entities": "^4.4.0"
 | 
					        "entities": "^4.4.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "path-browserify": {
 | 
				
			||||||
 | 
					      "version": "1.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "path-exists": {
 | 
					    "path-exists": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 | 
				
			||||||
@ -14446,8 +14586,7 @@
 | 
				
			|||||||
    "prettier": {
 | 
					    "prettier": {
 | 
				
			||||||
      "version": "2.8.0",
 | 
					      "version": "2.8.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
 | 
					      "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "pretty-format": {
 | 
					    "pretty-format": {
 | 
				
			||||||
      "version": "27.5.1",
 | 
					      "version": "27.5.1",
 | 
				
			||||||
@ -15456,6 +15595,12 @@
 | 
				
			|||||||
        "rollup": "^2.79.1"
 | 
					        "rollup": "^2.79.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "vite-plugin-monaco-editor": {
 | 
				
			||||||
 | 
					      "version": "1.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
 | 
				
			||||||
 | 
					      "requires": {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "vitest": {
 | 
					    "vitest": {
 | 
				
			||||||
      "version": "0.25.3",
 | 
					      "version": "0.25.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.3.tgz",
 | 
				
			||||||
@ -15478,6 +15623,21 @@
 | 
				
			|||||||
        "vite": "^3.0.0"
 | 
					        "vite": "^3.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "vscode-languageserver-textdocument": {
 | 
				
			||||||
 | 
					      "version": "1.0.7",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.7.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "vscode-languageserver-types": {
 | 
				
			||||||
 | 
					      "version": "3.17.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "vscode-uri": {
 | 
				
			||||||
 | 
					      "version": "3.0.6",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "w3c-xmlserializer": {
 | 
					    "w3c-xmlserializer": {
 | 
				
			||||||
      "version": "4.0.0",
 | 
					      "version": "4.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,8 @@
 | 
				
			|||||||
    "preact-router": "^4.1.0",
 | 
					    "preact-router": "^4.1.0",
 | 
				
			||||||
    "react": "npm:@preact/compat@^17.1.2",
 | 
					    "react": "npm:@preact/compat@^17.1.2",
 | 
				
			||||||
    "react-dom": "npm:@preact/compat@^17.1.2",
 | 
					    "react-dom": "npm:@preact/compat@^17.1.2",
 | 
				
			||||||
 | 
					    "vite-plugin-monaco-editor": "^1.1.0",
 | 
				
			||||||
 | 
					    "monaco-yaml": "^4.0.2",
 | 
				
			||||||
    "swr": "^1.3.0",
 | 
					    "swr": "^1.3.0",
 | 
				
			||||||
    "video.js": "^7.20.3",
 | 
					    "video.js": "^7.20.3",
 | 
				
			||||||
    "videojs-playlist": "^5.0.0",
 | 
					    "videojs-playlist": "^5.0.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -44,8 +44,10 @@ export default function Sidebar() {
 | 
				
			|||||||
      </Match>
 | 
					      </Match>
 | 
				
			||||||
      {birdseye?.enabled ? <Destination href="/birdseye" text="Birdseye" /> : null}
 | 
					      {birdseye?.enabled ? <Destination href="/birdseye" text="Birdseye" /> : null}
 | 
				
			||||||
      <Destination href="/events" text="Events" />
 | 
					      <Destination href="/events" text="Events" />
 | 
				
			||||||
 | 
					      <Separator />
 | 
				
			||||||
      <Destination href="/storage" text="Storage" />
 | 
					      <Destination href="/storage" text="Storage" />
 | 
				
			||||||
      <Destination href="/system" text="System" />
 | 
					      <Destination href="/system" text="System" />
 | 
				
			||||||
 | 
					      <Destination href="/config" text="Config" />
 | 
				
			||||||
      <Separator />
 | 
					      <Separator />
 | 
				
			||||||
      <div className="flex flex-grow" />
 | 
					      <div className="flex flex-grow" />
 | 
				
			||||||
      {ENV !== 'production' ? (
 | 
					      {ENV !== 'production' ? (
 | 
				
			||||||
 | 
				
			|||||||
@ -37,6 +37,7 @@ export default function App() {
 | 
				
			|||||||
                  />
 | 
					                  />
 | 
				
			||||||
                  <AsyncRoute path="/storage" getComponent={Routes.getStorage} />
 | 
					                  <AsyncRoute path="/storage" getComponent={Routes.getStorage} />
 | 
				
			||||||
                  <AsyncRoute path="/system" getComponent={Routes.getSystem} />
 | 
					                  <AsyncRoute path="/system" getComponent={Routes.getSystem} />
 | 
				
			||||||
 | 
					                  <AsyncRoute path="/config" getComponent={Routes.getConfig} />
 | 
				
			||||||
                  <AsyncRoute path="/styleguide" getComponent={Routes.getStyleGuide} />
 | 
					                  <AsyncRoute path="/styleguide" getComponent={Routes.getStyleGuide} />
 | 
				
			||||||
                  <Cameras default path="/" />
 | 
					                  <Cameras default path="/" />
 | 
				
			||||||
                </Router>
 | 
					                </Router>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										99
									
								
								web/src/routes/Config.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								web/src/routes/Config.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					import { h } from 'preact';
 | 
				
			||||||
 | 
					import useSWR from 'swr';
 | 
				
			||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					import { useApiHost } from '../api';
 | 
				
			||||||
 | 
					import ActivityIndicator from '../components/ActivityIndicator';
 | 
				
			||||||
 | 
					import Heading from '../components/Heading';
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'preact/hooks';
 | 
				
			||||||
 | 
					import Button from '../components/Button';
 | 
				
			||||||
 | 
					import { editor, Uri } from 'monaco-editor';
 | 
				
			||||||
 | 
					import { setDiagnosticsOptions } from 'monaco-yaml';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Config() {
 | 
				
			||||||
 | 
					  const apiHost = useApiHost();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { data: config } = useSWR('config/raw');
 | 
				
			||||||
 | 
					  const [success, setSuccess] = useState();
 | 
				
			||||||
 | 
					  const [error, setError] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onHandleSaveConfig = async (e) => {
 | 
				
			||||||
 | 
					    if (e) {
 | 
				
			||||||
 | 
					      e.stopPropagation();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    axios
 | 
				
			||||||
 | 
					      .post('config/save', window.editor.getValue(), {
 | 
				
			||||||
 | 
					        headers: { 'Content-Type': 'text/plain' },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .then((response) => {
 | 
				
			||||||
 | 
					        if (response.status === 200) {
 | 
				
			||||||
 | 
					          setSuccess(response.data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch((error) => {
 | 
				
			||||||
 | 
					        if (error.response) {
 | 
				
			||||||
 | 
					          setError(error.response.data.message);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          setError(error.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleCopyConfig = async () => {
 | 
				
			||||||
 | 
					    await window.navigator.clipboard.writeText(window.editor.getValue());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!config) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const modelUri = Uri.parse('a://b/api/config/schema.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setDiagnosticsOptions({
 | 
				
			||||||
 | 
					      enableSchemaRequest: true,
 | 
				
			||||||
 | 
					      hover: true,
 | 
				
			||||||
 | 
					      completion: true,
 | 
				
			||||||
 | 
					      validate: true,
 | 
				
			||||||
 | 
					      format: true,
 | 
				
			||||||
 | 
					      schemas: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          uri: `${apiHost}/api/config/schema.json`,
 | 
				
			||||||
 | 
					          fileMatch: [String(modelUri)],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.editor = editor.create(document.getElementById('container'), {
 | 
				
			||||||
 | 
					      language: 'yaml',
 | 
				
			||||||
 | 
					      model: editor.createModel(config, 'yaml', modelUri),
 | 
				
			||||||
 | 
					      scrollBeyondLastLine: false,
 | 
				
			||||||
 | 
					      theme: 'vs-dark',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!config) {
 | 
				
			||||||
 | 
					    return <ActivityIndicator />;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="space-y-4 p-2 px-4 h-full">
 | 
				
			||||||
 | 
					      <div className="flex justify-between">
 | 
				
			||||||
 | 
					        <Heading>Config</Heading>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          <Button className="mx-2" onClick={(e) => handleCopyConfig(e)}>
 | 
				
			||||||
 | 
					            Copy Config
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					          <Button className="mx-2" onClick={(e) => onHandleSaveConfig(e)}>
 | 
				
			||||||
 | 
					            Save & Restart
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {success && <div className="max-h-20 text-green-500">{success}</div>}
 | 
				
			||||||
 | 
					      {error && <div className="p-4 overflow-scroll text-red-500 whitespace-pre-wrap">{error}</div>}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div id="container" className="h-full" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,7 +7,7 @@ import { useWs } from '../api/ws';
 | 
				
			|||||||
import useSWR from 'swr';
 | 
					import useSWR from 'swr';
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table';
 | 
					import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table';
 | 
				
			||||||
import { useCallback, useState } from 'preact/hooks';
 | 
					import { useState } from 'preact/hooks';
 | 
				
			||||||
import Dialog from '../components/Dialog';
 | 
					import Dialog from '../components/Dialog';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emptyObject = Object.freeze({});
 | 
					const emptyObject = Object.freeze({});
 | 
				
			||||||
@ -34,13 +34,6 @@ export default function System() {
 | 
				
			|||||||
  const gpuNames = Object.keys(gpu_usages || emptyObject);
 | 
					  const gpuNames = Object.keys(gpu_usages || emptyObject);
 | 
				
			||||||
  const cameraNames = Object.keys(cameras || emptyObject);
 | 
					  const cameraNames = Object.keys(cameras || emptyObject);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleCopyConfig = useCallback(() => {
 | 
					 | 
				
			||||||
    async function copy() {
 | 
					 | 
				
			||||||
      await window.navigator.clipboard.writeText(JSON.stringify(config, null, 2));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    copy();
 | 
					 | 
				
			||||||
  }, [config]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onHandleFfprobe = async (camera, e) => {
 | 
					  const onHandleFfprobe = async (camera, e) => {
 | 
				
			||||||
    if (e) {
 | 
					    if (e) {
 | 
				
			||||||
      e.stopPropagation();
 | 
					      e.stopPropagation();
 | 
				
			||||||
@ -267,16 +260,6 @@ export default function System() {
 | 
				
			|||||||
          <p>System stats update automatically every {config.mqtt.stats_interval} seconds.</p>
 | 
					          <p>System stats update automatically every {config.mqtt.stats_interval} seconds.</p>
 | 
				
			||||||
        </Fragment>
 | 
					        </Fragment>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div className="relative">
 | 
					 | 
				
			||||||
        <Heading size="sm">Config</Heading>
 | 
					 | 
				
			||||||
        <Button className="absolute top-8 right-4" onClick={handleCopyConfig}>
 | 
					 | 
				
			||||||
          Copy to Clipboard
 | 
					 | 
				
			||||||
        </Button>
 | 
					 | 
				
			||||||
        <pre className="overflow-auto font-mono text-gray-900 dark:text-gray-100 rounded bg-gray-100 dark:bg-gray-800 p-2 max-h-96">
 | 
					 | 
				
			||||||
          {JSON.stringify(config, null, 2)}
 | 
					 | 
				
			||||||
        </pre>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,11 @@ export async function getStorage(_url, _cb, _props) {
 | 
				
			|||||||
  return module.default;
 | 
					  return module.default;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getConfig(_url, _cb, _props) {
 | 
				
			||||||
 | 
					  const module = await import('./Config.jsx');
 | 
				
			||||||
 | 
					  return module.default;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getStyleGuide(_url, _cb, _props) {
 | 
					export async function getStyleGuide(_url, _cb, _props) {
 | 
				
			||||||
  const module = await import('./StyleGuide.jsx');
 | 
					  const module = await import('./StyleGuide.jsx');
 | 
				
			||||||
  return module.default;
 | 
					  return module.default;
 | 
				
			||||||
 | 
				
			|||||||
@ -2,13 +2,16 @@
 | 
				
			|||||||
import path from "path";
 | 
					import path from "path";
 | 
				
			||||||
import { defineConfig } from 'vite'
 | 
					import { defineConfig } from 'vite'
 | 
				
			||||||
import preact from '@preact/preset-vite'
 | 
					import preact from '@preact/preset-vite'
 | 
				
			||||||
 | 
					import monacoEditorPlugin from 'vite-plugin-monaco-editor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://vitejs.dev/config/
 | 
					// https://vitejs.dev/config/
 | 
				
			||||||
export default defineConfig({
 | 
					export default defineConfig({
 | 
				
			||||||
  define: {
 | 
					  define: {
 | 
				
			||||||
    'import.meta.vitest': 'undefined',
 | 
					    'import.meta.vitest': 'undefined',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: [preact()],
 | 
					  plugins: [preact(), monacoEditorPlugin.default({
 | 
				
			||||||
 | 
					    customWorkers: [{label: 'yaml', entry: 'monaco-yaml/yaml.worker'}]
 | 
				
			||||||
 | 
					  })],
 | 
				
			||||||
  test: {
 | 
					  test: {
 | 
				
			||||||
    environment: 'jsdom',
 | 
					    environment: 'jsdom',
 | 
				
			||||||
    alias: {
 | 
					    alias: {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user