From c1f14e2d8725ee7a055ae405d001b51df8cce625 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 12 Dec 2023 16:22:02 -0700 Subject: [PATCH] Support config editor in new webUI (#8928) * Add config editor to new webUI and fix double scroll * Address review comments and enable light theme for vscode --- web-new/package-lock.json | 122 ++++++++++++++++++++++- web-new/package.json | 1 + web-new/src/pages/ConfigEditor.tsx | 152 ++++++++++++++++++++++++++++- 3 files changed, 268 insertions(+), 7 deletions(-) diff --git a/web-new/package-lock.json b/web-new/package-lock.json index a5cf68b36..25208d2f5 100644 --- a/web-new/package-lock.json +++ b/web-new/package-lock.json @@ -30,6 +30,7 @@ "idb-keyval": "^6.2.1", "immer": "^10.0.3", "lucide-react": "^0.294.0", + "monaco-yaml": "^5.1.0", "react": "^18.2.0", "react-day-picker": "^8.9.1", "react-dom": "^18.2.0", @@ -2199,8 +2200,7 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/node": { "version": "20.10.3", @@ -5108,8 +5108,7 @@ "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==", - "dev": true + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, "node_modules/keycode": { "version": "2.2.0", @@ -5433,6 +5432,84 @@ "integrity": "sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==", "peer": true }, + "node_modules/monaco-languageserver-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/monaco-languageserver-types/-/monaco-languageserver-types-0.2.3.tgz", + "integrity": "sha512-QyV5R7s+rJ87bX1sRioMJZULWiTnMp0Vm+RLILgMEL0SqWuBsQBSW0EZunr4xMZhv6Qun3UZNCN4JrCCLURcgQ==", + "dependencies": { + "monaco-types": "^0.1.0", + "vscode-languageserver-protocol": "^3.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "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-types": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/monaco-types/-/monaco-types-0.1.0.tgz", + "integrity": "sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==", + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "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": "5.1.0", + "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-5.1.0.tgz", + "integrity": "sha512-DU+cgXSJdOFKQ4I4oLg0V+mHKq1dJX+7hbIE4fJsgegUf1zEHW3PNlGj6qabUU2HZIPJ5NmXEf005GU9YDzTYQ==", + "dependencies": { + "@types/json-schema": "^7.0.0", + "jsonc-parser": "^3.0.0", + "monaco-languageserver-types": "^0.2.0", + "monaco-marker-data-provider": "^1.0.0", + "monaco-types": "^0.1.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.36" + } + }, + "node_modules/monaco-yaml/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/mpd-parser": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.2.2.tgz", @@ -5812,6 +5889,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", @@ -7743,6 +7825,38 @@ } } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/web-new/package.json b/web-new/package.json index 63287105a..12228b554 100644 --- a/web-new/package.json +++ b/web-new/package.json @@ -35,6 +35,7 @@ "idb-keyval": "^6.2.1", "immer": "^10.0.3", "lucide-react": "^0.294.0", + "monaco-yaml": "^5.1.0", "react": "^18.2.0", "react-day-picker": "^8.9.1", "react-dom": "^18.2.0", diff --git a/web-new/src/pages/ConfigEditor.tsx b/web-new/src/pages/ConfigEditor.tsx index 6834f96aa..59577b647 100644 --- a/web-new/src/pages/ConfigEditor.tsx +++ b/web-new/src/pages/ConfigEditor.tsx @@ -1,10 +1,156 @@ +import useSWR from "swr"; +import * as monaco from "monaco-editor"; +import { configureMonacoYaml } from "monaco-yaml"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useApiHost } from "@/api"; import Heading from "@/components/ui/heading"; +import ActivityIndicator from "@/components/ui/activity-indicator"; +import { Button } from "@/components/ui/button"; +import axios from "axios"; +import copy from "copy-to-clipboard"; +import { useTheme } from "@/context/theme-provider"; + +type SaveOptions = "saveonly" | "restart"; function ConfigEditor() { + const apiHost = useApiHost(); + + const { data: config } = useSWR("config/raw"); + + const { theme } = useTheme(); + const [success, setSuccess] = useState(); + const [error, setError] = useState(); + + const editorRef = useRef(); + const modelRef = useRef(); + + const onHandleSaveConfig = useCallback( + async (save_option: SaveOptions) => { + if (!editorRef.current) { + return; + } + + axios + .post( + `config/save?save_option=${save_option}`, + editorRef.current.getValue(), + { + headers: { "Content-Type": "text/plain" }, + } + ) + .then((response) => { + if (response.status === 200) { + setError(""); + setSuccess(response.data.message); + } + }) + .catch((error) => { + setSuccess(""); + + if (error.response) { + setError(error.response.data.message); + } else { + setError(error.message); + } + }); + }, + [editorRef] + ); + + const handleCopyConfig = useCallback(async () => { + if (!editorRef.current) { + return; + } + + copy(editorRef.current.getValue()); + }, [editorRef]); + + useEffect(() => { + if (!config) { + return; + } + + if (modelRef.current != null) { + // we don't need to recreate the editor if it already exists + return; + } + + const modelUri = monaco.Uri.parse("a://b/api/config/schema.json"); + + if (monaco.editor.getModels().length > 0) { + modelRef.current = monaco.editor.getModel(modelUri); + } else { + modelRef.current = monaco.editor.createModel(config, "yaml", modelUri); + } + + configureMonacoYaml(monaco, { + enableSchemaRequest: true, + hover: true, + completion: true, + validate: true, + format: true, + schemas: [ + { + uri: `${apiHost}api/config/schema.json`, + fileMatch: [String(modelUri)], + }, + ], + }); + + const container = document.getElementById("container"); + + if (container != undefined) { + editorRef.current = monaco.editor.create(container, { + language: "yaml", + model: modelRef.current, + scrollBeyondLastLine: false, + theme: theme == "dark" ? "vs-dark" : "vs-light", + }); + } + }); + + if (!config) { + return ; + } + return ( - <> - Configuration Editor - +
+
+ Config +
+ + + +
+
+ + {success &&
{success}
} + {error && ( +
+ {error} +
+ )} + +
+
); }