diff --git a/web/package.json b/web/package.json
index d7ce6752..6cb4c741 100644
--- a/web/package.json
+++ b/web/package.json
@@ -37,7 +37,6 @@
"@xterm/addon-web-links": "^0.12.0",
"@xterm/addon-webgl": "^0.19.0",
"@xterm/xterm": "^6.0.0",
- "ace-builds": "^1.43.5",
"alova": "^3.3.4",
"echarts": "^6.0.0",
"install": "^0.13.0",
@@ -45,6 +44,8 @@
"luxon": "^3.7.2",
"marked": "^17.0.0",
"mitt": "^3.0.1",
+ "monaco-editor": "^0.55.1",
+ "monaco-editor-nginx": "^2.0.2",
"node-forge": "^1.3.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
@@ -52,7 +53,6 @@
"vue": "^3.5.22",
"vue-echarts": "^8.0.1",
"vue-router": "^4.6.3",
- "vue3-ace-editor": "^2.2.4",
"vue3-gettext": "4.0.0-beta.1"
},
"devDependencies": {
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index e8c155ad..0ba246c0 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -44,9 +44,6 @@ importers:
'@xterm/xterm':
specifier: ^6.0.0
version: 6.0.0
- ace-builds:
- specifier: ^1.43.5
- version: 1.43.5
alova:
specifier: ^3.3.4
version: 3.4.1
@@ -68,6 +65,12 @@ importers:
mitt:
specifier: ^3.0.1
version: 3.0.1
+ monaco-editor:
+ specifier: ^0.55.1
+ version: 0.55.1
+ monaco-editor-nginx:
+ specifier: ^2.0.2
+ version: 2.0.2(@babel/runtime@7.28.4)(@nginx/reference-lib@1.1.25)(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
node-forge:
specifier: ^1.3.1
version: 1.3.3
@@ -89,9 +92,6 @@ importers:
vue-router:
specifier: ^4.6.3
version: 4.6.4(vue@3.5.26(typescript@5.9.3))
- vue3-ace-editor:
- specifier: ^2.2.4
- version: 2.2.4(ace-builds@1.43.5)(vue@3.5.26(typescript@5.9.3))
vue3-gettext:
specifier: 4.0.0-beta.1
version: 4.0.0-beta.1(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3))
@@ -345,6 +345,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -779,6 +783,9 @@ packages:
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+ '@nginx/reference-lib@1.1.25':
+ resolution: {integrity: sha512-y8g/3Z17VKCvnGB2KRtAvlcEWvRUaO0Ln5MIAdBsqX6qsfMIDznFt0lnFQMVZM51AQgQeY4TP+nWTOiKe6+wsQ==}
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1039,6 +1046,9 @@ packages:
'@types/node@24.10.4':
resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==}
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
@@ -1350,9 +1360,6 @@ packages:
'@xterm/xterm@6.0.0':
resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==}
- ace-builds@1.43.5:
- resolution: {integrity: sha512-iH5FLBKdB7SVn9GR37UgA/tpQS8OTWIxWAuq3Ofaw+Qbc69FfPXsXd9jeW7KRG2xKpKMqBDnu0tHBrCWY5QI7A==}
-
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1716,6 +1723,9 @@ packages:
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
engines: {node: '>= 4'}
+ dompurify@3.2.7:
+ resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
+
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@@ -2414,6 +2424,11 @@ packages:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
+ marked@14.0.0:
+ resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
marked@17.0.1:
resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==}
engines: {node: '>= 20'}
@@ -2481,6 +2496,18 @@ packages:
resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==}
hasBin: true
+ monaco-editor-nginx@2.0.2:
+ resolution: {integrity: sha512-F/qejb0w0hxIyxbu2JMCe+MhnOV7bxGbUynnyMXk7Cy25Na5fD99u5QqRr7WfRhIn0xcdhBEkJ/jikQsxVnEOA==}
+ peerDependencies:
+ '@babel/runtime': '>=7.10.0'
+ '@nginx/reference-lib': '>=1.1.0'
+ monaco-editor: '>=0.22.3'
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ monaco-editor@0.55.1:
+ resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
+
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
@@ -2749,6 +2776,15 @@ packages:
rate-limiter-flexible@5.0.5:
resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
+ react-dom@19.2.3:
+ resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
+ peerDependencies:
+ react: ^19.2.3
+
+ react@19.2.3:
+ resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
+ engines: {node: '>=0.10.0'}
+
read-package-json-fast@4.0.0:
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
engines: {node: ^18.17.0 || >=20.5.0}
@@ -2780,9 +2816,6 @@ packages:
remove@0.1.5:
resolution: {integrity: sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ==}
- resize-observer-polyfill@1.5.1:
- resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -2834,6 +2867,9 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
@@ -3306,12 +3342,6 @@ packages:
peerDependencies:
typescript: '>=5.0.0'
- vue3-ace-editor@2.2.4:
- resolution: {integrity: sha512-FZkEyfpbH068BwjhMyNROxfEI8135Sc+x8ouxkMdCNkuj/Tuw83VP/gStFQqZHqljyX9/VfMTCdTqtOnJZGN8g==}
- peerDependencies:
- ace-builds: '*'
- vue: ^3
-
vue3-gettext@4.0.0-beta.1:
resolution: {integrity: sha512-1A46SmubgTMyy7i5hj8ay50NFl6/vzwoIVZPuGCin/X3a/NVCAs99G0EbcnfJiR7NZNTJgUjvBzppufC7Kq+4A==}
engines: {node: '>= 20.19.0'}
@@ -3586,6 +3616,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/runtime@7.28.4': {}
+
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -4169,6 +4201,8 @@ snapshots:
'@marijn/find-cluster-break@1.0.2': {}
+ '@nginx/reference-lib@1.1.25': {}
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4358,6 +4392,9 @@ snapshots:
dependencies:
undici-types: 7.16.0
+ '@types/trusted-types@2.0.7':
+ optional: true
+
'@types/web-bluetooth@0.0.21': {}
'@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
@@ -4836,8 +4873,6 @@ snapshots:
'@xterm/xterm@6.0.0': {}
- ace-builds@1.43.5: {}
-
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
@@ -5199,6 +5234,10 @@ snapshots:
dependencies:
domelementtype: 2.3.0
+ dompurify@3.2.7:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
domutils@2.8.0:
dependencies:
dom-serializer: 1.4.1
@@ -5952,6 +5991,8 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
+ marked@14.0.0: {}
+
marked@17.0.1: {}
math-intrinsics@1.1.0: {}
@@ -6029,6 +6070,19 @@ snapshots:
dependencies:
commander: 14.0.2
+ monaco-editor-nginx@2.0.2(@babel/runtime@7.28.4)(@nginx/reference-lib@1.1.25)(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@nginx/reference-lib': 1.1.25
+ monaco-editor: 0.55.1
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+
+ monaco-editor@0.55.1:
+ dependencies:
+ dompurify: 3.2.7
+ marked: 14.0.0
+
mrmime@2.0.1: {}
ms@2.0.0: {}
@@ -6279,6 +6333,13 @@ snapshots:
rate-limiter-flexible@5.0.5: {}
+ react-dom@19.2.3(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ scheduler: 0.27.0
+
+ react@19.2.3: {}
+
read-package-json-fast@4.0.0:
dependencies:
json-parse-even-better-errors: 4.0.0
@@ -6318,8 +6379,6 @@ snapshots:
dependencies:
seq: 0.3.5
- resize-observer-polyfill@1.5.1: {}
-
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -6397,6 +6456,8 @@ snapshots:
optionalDependencies:
'@parcel/watcher': 2.5.1
+ scheduler@0.27.0: {}
+
scule@1.3.0: {}
seemly@0.3.10: {}
@@ -6953,12 +7014,6 @@ snapshots:
'@vue/language-core': 3.2.1
typescript: 5.9.3
- vue3-ace-editor@2.2.4(ace-builds@1.43.5)(vue@3.5.26(typescript@5.9.3)):
- dependencies:
- ace-builds: 1.43.5
- resize-observer-polyfill: 1.5.1
- vue: 3.5.26(typescript@5.9.3)
-
vue3-gettext@4.0.0-beta.1(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3)):
dependencies:
'@vue/compiler-sfc': 3.5.26
diff --git a/web/src/components/common/CommonEditor.vue b/web/src/components/common/CommonEditor.vue
index e862a0ab..f779100e 100644
--- a/web/src/components/common/CommonEditor.vue
+++ b/web/src/components/common/CommonEditor.vue
@@ -1,6 +1,7 @@
-
+
-
+
diff --git a/web/src/components/common/FileEditor.vue b/web/src/components/common/FileEditor.vue
index 56969485..37b69be0 100644
--- a/web/src/components/common/FileEditor.vue
+++ b/web/src/components/common/FileEditor.vue
@@ -1,9 +1,11 @@
-
+
-
+
diff --git a/web/src/utils/file/index.ts b/web/src/utils/file/index.ts
index 49c812e2..c3d8115e 100644
--- a/web/src/utils/file/index.ts
+++ b/web/src/utils/file/index.ts
@@ -160,6 +160,9 @@ const languageByPath = (path: string) => {
return 'html'
case 'ini':
case 'conf':
+ if (path.toLowerCase().includes('nginx')) {
+ return 'nginx'
+ }
return 'ini'
case 'java':
return 'java'
diff --git a/web/src/utils/monaco/index.ts b/web/src/utils/monaco/index.ts
new file mode 100644
index 00000000..886cd19a
--- /dev/null
+++ b/web/src/utils/monaco/index.ts
@@ -0,0 +1,115 @@
+import type * as Monaco from 'monaco-editor'
+
+let monacoInstance: typeof Monaco | null = null
+let isInitialized = false
+let initPromise: Promise | null = null
+
+async function loadMonacoLocale(locale: string) {
+ switch (locale) {
+ case 'cs':
+ await import('monaco-editor/esm/nls.messages.cs.js')
+ break
+ case 'de':
+ await import('monaco-editor/esm/nls.messages.de.js')
+ break
+ case 'es':
+ await import('monaco-editor/esm/nls.messages.es.js')
+ break
+ case 'fr':
+ await import('monaco-editor/esm/nls.messages.fr.js')
+ break
+ case 'it':
+ await import('monaco-editor/esm/nls.messages.it.js')
+ break
+ case 'ja':
+ await import('monaco-editor/esm/nls.messages.ja.js')
+ break
+ case 'ko':
+ await import('monaco-editor/esm/nls.messages.ko.js')
+ break
+ case 'pl':
+ await import('monaco-editor/esm/nls.messages.pl.js')
+ break
+ case 'pt_BR':
+ await import('monaco-editor/esm/nls.messages.pt-br.js')
+ break
+ case 'ru':
+ await import('monaco-editor/esm/nls.messages.ru.js')
+ break
+ case 'tr':
+ await import('monaco-editor/esm/nls.messages.tr.js')
+ break
+ case 'zh_CN':
+ await import('monaco-editor/esm/nls.messages.zh-cn.js')
+ break
+ case 'zh_TW':
+ await import('monaco-editor/esm/nls.messages.zh-tw.js')
+ break
+ default:
+ break
+ }
+}
+
+async function setupMonacoWorkers() {
+ if (self.MonacoEnvironment) return
+
+ const [editorWorker, jsonWorker, cssWorker, htmlWorker, tsWorker] = await Promise.all([
+ import('monaco-editor/esm/vs/editor/editor.worker?worker'),
+ import('monaco-editor/esm/vs/language/json/json.worker?worker'),
+ import('monaco-editor/esm/vs/language/css/css.worker?worker'),
+ import('monaco-editor/esm/vs/language/html/html.worker?worker'),
+ import('monaco-editor/esm/vs/language/typescript/ts.worker?worker')
+ ])
+
+ self.MonacoEnvironment = {
+ getWorker(_: any, label: string) {
+ if (label === 'json') {
+ return new jsonWorker.default()
+ }
+ if (label === 'css' || label === 'scss' || label === 'less') {
+ return new cssWorker.default()
+ }
+ if (label === 'html' || label === 'handlebars' || label === 'razor') {
+ return new htmlWorker.default()
+ }
+ if (label === 'typescript' || label === 'javascript') {
+ return new tsWorker.default()
+ }
+ return new editorWorker.default()
+ }
+ }
+}
+
+/**
+ * 获取 Monaco 实例
+ * @param locale 可选的语言设置
+ * @returns Monaco 实例
+ */
+export async function getMonaco(locale?: string): Promise {
+ if (isInitialized && monacoInstance) {
+ return monacoInstance
+ }
+
+ if (initPromise) {
+ return initPromise
+ }
+
+ initPromise = (async () => {
+ if (locale) {
+ await loadMonacoLocale(locale)
+ }
+
+ await setupMonacoWorkers()
+
+ const monaco = await import('monaco-editor')
+ await import('monaco-editor-nginx')
+ monacoInstance = monaco
+ isInitialized = true
+
+ return monaco
+ })()
+
+ return initPromise
+}
+
+export type { Monaco }
diff --git a/web/types/monaco.d.ts b/web/types/monaco.d.ts
new file mode 100644
index 00000000..656fa9a6
--- /dev/null
+++ b/web/types/monaco.d.ts
@@ -0,0 +1,36 @@
+// Monaco Editor 本地化模块声明
+declare module 'monaco-editor/esm/nls.messages.cs.js'
+declare module 'monaco-editor/esm/nls.messages.de.js'
+declare module 'monaco-editor/esm/nls.messages.es.js'
+declare module 'monaco-editor/esm/nls.messages.fr.js'
+declare module 'monaco-editor/esm/nls.messages.it.js'
+declare module 'monaco-editor/esm/nls.messages.ja.js'
+declare module 'monaco-editor/esm/nls.messages.ko.js'
+declare module 'monaco-editor/esm/nls.messages.pl.js'
+declare module 'monaco-editor/esm/nls.messages.pt-br.js'
+declare module 'monaco-editor/esm/nls.messages.ru.js'
+declare module 'monaco-editor/esm/nls.messages.tr.js'
+declare module 'monaco-editor/esm/nls.messages.zh-cn.js'
+declare module 'monaco-editor/esm/nls.messages.zh-tw.js'
+
+// Monaco Editor Worker 模块声明
+declare module 'monaco-editor/esm/vs/editor/editor.worker?worker' {
+ const EditorWorker: new () => Worker
+ export default EditorWorker
+}
+declare module 'monaco-editor/esm/vs/language/json/json.worker?worker' {
+ const JsonWorker: new () => Worker
+ export default JsonWorker
+}
+declare module 'monaco-editor/esm/vs/language/css/css.worker?worker' {
+ const CssWorker: new () => Worker
+ export default CssWorker
+}
+declare module 'monaco-editor/esm/vs/language/html/html.worker?worker' {
+ const HtmlWorker: new () => Worker
+ export default HtmlWorker
+}
+declare module 'monaco-editor/esm/vs/language/typescript/ts.worker?worker' {
+ const TsWorker: new () => Worker
+ export default TsWorker
+}