diff --git a/frigate/config.py b/frigate/config.py
index 3e31b1fcb..2ea17ebe2 100644
--- a/frigate/config.py
+++ b/frigate/config.py
@@ -1028,3 +1028,8 @@ class FrigateConfig(FrigateBaseModel):
config = json.loads(raw_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)
diff --git a/frigate/http.py b/frigate/http.py
index a1f9aea4e..f5e9eaa7a 100644
--- a/frigate/http.py
+++ b/frigate/http.py
@@ -7,6 +7,7 @@ import json
import os
import subprocess as sp
import time
+import traceback
from functools import reduce
from pathlib import Path
from urllib.parse import unquote
@@ -27,11 +28,17 @@ from flask import (
from peewee import SqliteDatabase, operator, fn, DoesNotExist
from playhouse.shortcuts import model_to_dict
+from frigate.config import FrigateConfig
from frigate.const import CLIPS_DIR, RECORD_DIR
from frigate.models import Event, Recordings
from frigate.object_processing import TrackedObject
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.version import VERSION
@@ -599,7 +606,81 @@ def 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():
return current_app.response_class(
current_app.frigate_config.schema_json(), mimetype="application/json"
diff --git a/web/package-lock.json b/web/package-lock.json
index e6f47b219..e83855174 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -13,6 +13,7 @@
"date-fns": "^2.29.3",
"idb-keyval": "^6.2.0",
"immer": "^9.0.16",
+ "monaco-yaml": "^4.0.2",
"preact": "^10.11.3",
"preact-async-route": "^2.2.1",
"preact-router": "^4.1.0",
@@ -21,7 +22,8 @@
"swr": "^1.3.0",
"video.js": "^7.20.3",
"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": {
"@preact/preset-vite": "^2.4.0",
@@ -1361,8 +1363,7 @@
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
- "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
- "dev": true
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"node_modules/@types/ms": {
"version": "0.7.31",
@@ -6257,6 +6258,11 @@
"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": {
"version": "3.3.3",
"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"
}
},
+ "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": {
"version": "0.21.1",
"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"
}
},
+ "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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -7438,7 +7505,6 @@
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
"integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
- "dev": true,
"bin": {
"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": {
"version": "0.25.3",
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
@@ -10160,8 +10249,7 @@
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
- "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
- "dev": true
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"@types/ms": {
"version": "0.7.31",
@@ -13622,6 +13710,11 @@
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"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": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
@@ -13875,6 +13968,48 @@
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
"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": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
@@ -14287,6 +14422,11 @@
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -14446,8 +14586,7 @@
"prettier": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
- "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
- "dev": true
+ "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA=="
},
"pretty-format": {
"version": "27.5.1",
@@ -15456,6 +15595,12 @@
"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": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.3.tgz",
@@ -15478,6 +15623,21 @@
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
diff --git a/web/package.json b/web/package.json
index b81d2fc96..5232dd876 100644
--- a/web/package.json
+++ b/web/package.json
@@ -22,6 +22,8 @@
"preact-router": "^4.1.0",
"react": "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",
"video.js": "^7.20.3",
"videojs-playlist": "^5.0.0",
diff --git a/web/src/Sidebar.jsx b/web/src/Sidebar.jsx
index c7d937da9..6c4fd1d92 100644
--- a/web/src/Sidebar.jsx
+++ b/web/src/Sidebar.jsx
@@ -44,8 +44,10 @@ export default function Sidebar() {
{birdseye?.enabled ?
System stats update automatically every {config.mqtt.stats_interval} seconds.
)} - -- {JSON.stringify(config, null, 2)} --