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 ? : null} + +
{ENV !== 'production' ? ( diff --git a/web/src/app.tsx b/web/src/app.tsx index 536cc82e3..bc7e1b562 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -37,6 +37,7 @@ export default function App() { /> + diff --git a/web/src/routes/Config.jsx b/web/src/routes/Config.jsx new file mode 100644 index 000000000..66c3d6117 --- /dev/null +++ b/web/src/routes/Config.jsx @@ -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 ; + } + + return ( +
+
+ Config +
+ + +
+
+ + {success &&
{success}
} + {error &&
{error}
} + +
+
+ ); +} diff --git a/web/src/routes/System.jsx b/web/src/routes/System.jsx index 0f7d78f9e..d5b7ce7d5 100644 --- a/web/src/routes/System.jsx +++ b/web/src/routes/System.jsx @@ -7,7 +7,7 @@ import { useWs } from '../api/ws'; import useSWR from 'swr'; import axios from 'axios'; 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'; const emptyObject = Object.freeze({}); @@ -34,13 +34,6 @@ export default function System() { const gpuNames = Object.keys(gpu_usages || 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) => { if (e) { e.stopPropagation(); @@ -267,16 +260,6 @@ export default function System() {

System stats update automatically every {config.mqtt.stats_interval} seconds.

)} - -
- Config - -
-          {JSON.stringify(config, null, 2)}
-        
-
); } diff --git a/web/src/routes/index.js b/web/src/routes/index.js index 39c9ed05c..1bf04f714 100644 --- a/web/src/routes/index.js +++ b/web/src/routes/index.js @@ -38,6 +38,11 @@ export async function getStorage(_url, _cb, _props) { 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) { const module = await import('./StyleGuide.jsx'); return module.default; diff --git a/web/vite.config.ts b/web/vite.config.ts index b9e024516..27d24ee8e 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -2,13 +2,16 @@ import path from "path"; import { defineConfig } from 'vite' import preact from '@preact/preset-vite' +import monacoEditorPlugin from 'vite-plugin-monaco-editor'; // https://vitejs.dev/config/ export default defineConfig({ define: { 'import.meta.vitest': 'undefined', }, - plugins: [preact()], + plugins: [preact(), monacoEditorPlugin.default({ + customWorkers: [{label: 'yaml', entry: 'monaco-yaml/yaml.worker'}] + })], test: { environment: 'jsdom', alias: {