From 0d8ef226c13bce5682273dbf94f5b6ec56ca8c2c Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 10 Jun 2025 21:46:50 +0200 Subject: [PATCH] Can edit Cloud init user data from UI --- virtweb_frontend/package-lock.json | 177 +++++++++++++++++- virtweb_frontend/package.json | 3 + virtweb_frontend/src/index.tsx | 42 ++++- .../src/widgets/forms/CloudInitEditor.tsx | 26 +++ 4 files changed, 237 insertions(+), 11 deletions(-) diff --git a/virtweb_frontend/package-lock.json b/virtweb_frontend/package-lock.json index c6ade62..3af0b01 100644 --- a/virtweb_frontend/package-lock.json +++ b/virtweb_frontend/package-lock.json @@ -13,6 +13,7 @@ "@fontsource/roboto": "^5.2.5", "@mdi/js": "^7.4.47", "@mdi/react": "^1.6.1", + "@monaco-editor/react": "^4.7.0", "@mui/icons-material": "^7.1.1", "@mui/material": "^7.1.1", "@mui/x-charts": "^8.3.1", @@ -20,6 +21,8 @@ "date-and-time": "^3.6.0", "filesize": "^10.1.6", "humanize-duration": "^3.32.2", + "monaco-editor": "^0.52.2", + "monaco-yaml": "^5.4.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.2", @@ -958,6 +961,29 @@ "prop-types": "^15.7.2" } }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz", @@ -3481,6 +3507,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3607,6 +3639,84 @@ "node": "*" } }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "license": "MIT" + }, + "node_modules/monaco-languageserver-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/monaco-languageserver-types/-/monaco-languageserver-types-0.4.0.tgz", + "integrity": "sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==", + "license": "MIT", + "dependencies": { + "monaco-types": "^0.1.0", + "vscode-languageserver-protocol": "^3.0.0", + "vscode-uri": "^3.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/monaco-marker-data-provider": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.2.4.tgz", + "integrity": "sha512-4DsPgsAqpTyUDs3humXRBPUJoihTv+L6v9aupQWD80X2YXaCXUd11mWYeSCYHuPgdUmjFaNWCEOjQ6ewf/QA1Q==", + "license": "MIT", + "dependencies": { + "monaco-types": "^0.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "peerDependencies": { + "monaco-editor": ">=0.30.0" + } + }, + "node_modules/monaco-yaml": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-5.4.0.tgz", + "integrity": "sha512-tuBVDy1KAPrgO905GHTItu8AaA5bIzF5S4X0JVRAE/D66FpRhkDUk7tKi5bwKMVTTugtpMLsXN4ewh4CgE/FtQ==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "jsonc-parser": "^3.0.0", + "monaco-languageserver-types": "^0.4.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": "^3.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/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3753,6 +3863,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3846,6 +3962,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -4326,6 +4457,12 @@ "node": ">=8" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/string-ts": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.2.1.tgz", @@ -4714,6 +4851,43 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4781,10 +4955,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "dev": true, "license": "ISC", - "optional": true, - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/virtweb_frontend/package.json b/virtweb_frontend/package.json index 8b7967a..969d8d4 100644 --- a/virtweb_frontend/package.json +++ b/virtweb_frontend/package.json @@ -15,6 +15,7 @@ "@fontsource/roboto": "^5.2.5", "@mdi/js": "^7.4.47", "@mdi/react": "^1.6.1", + "@monaco-editor/react": "^4.7.0", "@mui/icons-material": "^7.1.1", "@mui/material": "^7.1.1", "@mui/x-charts": "^8.3.1", @@ -22,6 +23,8 @@ "date-and-time": "^3.6.0", "filesize": "^10.1.6", "humanize-duration": "^3.32.2", + "monaco-editor": "^0.52.2", + "monaco-yaml": "^5.4.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.2", diff --git a/virtweb_frontend/src/index.tsx b/virtweb_frontend/src/index.tsx index a1e9196..9395a3a 100644 --- a/virtweb_frontend/src/index.tsx +++ b/virtweb_frontend/src/index.tsx @@ -3,16 +3,44 @@ import "@fontsource/roboto/400.css"; import "@fontsource/roboto/500.css"; import "@fontsource/roboto/700.css"; +import { ThemeProvider, createTheme } from "@mui/material"; import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "./App"; +import { AlertDialogProvider } from "./hooks/providers/AlertDialogProvider"; +import { ConfirmDialogProvider } from "./hooks/providers/ConfirmDialogProvider"; +import { LoadingMessageProvider } from "./hooks/providers/LoadingMessageProvider"; +import { SnackbarProvider } from "./hooks/providers/SnackbarProvider"; import "./index.css"; import { LoadServerConfig } from "./widgets/LoadServerConfig"; -import { ThemeProvider, createTheme } from "@mui/material"; -import { LoadingMessageProvider } from "./hooks/providers/LoadingMessageProvider"; -import { AlertDialogProvider } from "./hooks/providers/AlertDialogProvider"; -import { SnackbarProvider } from "./hooks/providers/SnackbarProvider"; -import { ConfirmDialogProvider } from "./hooks/providers/ConfirmDialogProvider"; + +import { loader } from "@monaco-editor/react"; +import * as monaco from "monaco-editor"; +import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; +import { configureMonacoYaml } from "monaco-yaml"; +import YamlWorker from "monaco-yaml/yaml.worker?worker"; + +// This allows to use a self hosted instance of Monaco editor +loader.config({ monaco }); + +// Add YAML support to Monaco +configureMonacoYaml(monaco, { + enableSchemaRequest: false, +}); + +/// YAML worker +window.MonacoEnvironment = { + getWorker(_moduleId, label) { + switch (label) { + case "editorWorkerService": + return new EditorWorker(); + case "yaml": + return new YamlWorker(); + default: + throw new Error(`Unknown label ${label}`); + } + }, +}; const darkTheme = createTheme({ palette: { @@ -20,9 +48,7 @@ const darkTheme = createTheme({ }, }); -const root = ReactDOM.createRoot( - document.getElementById("root")! -); +const root = ReactDOM.createRoot(document.getElementById("root")!); root.render( diff --git a/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx b/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx index 7e9a78e..987d3e6 100644 --- a/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx +++ b/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx @@ -1,3 +1,4 @@ +import Editor from "@monaco-editor/react"; import RefreshIcon from "@mui/icons-material/Refresh"; import { Grid, IconButton, InputAdornment, Tooltip } from "@mui/material"; import { v4 as uuidv4 } from "uuid"; @@ -33,6 +34,10 @@ export function CloudInitEditor(p: CloudInitProps): React.ReactElement { {...p} editable={p.editable && p.vm.cloud_init.attach_config} /> + ); @@ -100,3 +105,24 @@ function CloudInitMetadata(p: CloudInitProps): React.ReactElement { ); } + +function CloudInitRawUserData(p: CloudInitProps): React.ReactElement { + return ( + + { + p.vm.cloud_init.user_data = v ?? ""; + p.onChange?.(); + }} + /> + + ); +}