feat:对接ACS容器和虚拟机
This commit is contained in:
Generated
+28
-1
@@ -9,15 +9,18 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"@monaco-editor/loader": "^1.5.0",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"element-plus": "^2.9.1",
|
"element-plus": "^2.9.1",
|
||||||
|
"monaco-editor": "^0.52.2",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0",
|
||||||
|
"xterm": "^5.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
@@ -525,6 +528,14 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="
|
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@monaco-editor/loader": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
|
||||||
|
"dependencies": {
|
||||||
|
"state-local": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@popperjs/core": {
|
"node_modules/@popperjs/core": {
|
||||||
"name": "@sxzz/popperjs-es",
|
"name": "@sxzz/popperjs-es",
|
||||||
"version": "2.11.7",
|
"version": "2.11.7",
|
||||||
@@ -1655,6 +1666,11 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
|
||||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/monaco-editor": {
|
||||||
|
"version": "0.52.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.52.2.tgz",
|
||||||
|
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ=="
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
@@ -1894,6 +1910,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/state-local": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/state-local/-/state-local-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||||
@@ -2081,6 +2102,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xterm": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/xterm/-/xterm-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==",
|
||||||
|
"deprecated": "This package is now deprecated. Move to @xterm/xterm instead."
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
|
||||||
|
|||||||
+4
-1
@@ -10,15 +10,18 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"@monaco-editor/loader": "^1.5.0",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"element-plus": "^2.9.1",
|
"element-plus": "^2.9.1",
|
||||||
|
"monaco-editor": "^0.52.2",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0",
|
||||||
|
"xterm": "^5.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
|||||||
@@ -0,0 +1,646 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="editorContainer" class="monaco-editor-container" :style="{ height: height + 'px' }"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||||
|
import * as monaco from 'monaco-editor'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
default: 'plaintext'
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: 'vs-dark'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change', 'save'])
|
||||||
|
|
||||||
|
const editorContainer = ref(null)
|
||||||
|
let editor = null
|
||||||
|
|
||||||
|
// 默认编辑器选项
|
||||||
|
const defaultOptions = {
|
||||||
|
automaticLayout: true,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'SF Mono, Monaco, Cascadia Code, Roboto Mono, Consolas, Courier New, monospace',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
minimap: { enabled: true },
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
wordWrap: 'on',
|
||||||
|
tabSize: 2,
|
||||||
|
insertSpaces: true,
|
||||||
|
detectIndentation: true,
|
||||||
|
renderWhitespace: 'selection',
|
||||||
|
renderLineHighlight: 'all',
|
||||||
|
cursorBlinking: 'smooth',
|
||||||
|
cursorSmoothCaretAnimation: true,
|
||||||
|
smoothScrolling: true,
|
||||||
|
mouseWheelScrollSensitivity: 1,
|
||||||
|
fastScrollSensitivity: 5,
|
||||||
|
scrollbar: {
|
||||||
|
vertical: 'visible',
|
||||||
|
horizontal: 'visible',
|
||||||
|
useShadows: false,
|
||||||
|
verticalHasArrows: false,
|
||||||
|
horizontalHasArrows: false,
|
||||||
|
verticalScrollbarSize: 10,
|
||||||
|
horizontalScrollbarSize: 10
|
||||||
|
},
|
||||||
|
// 语法高亮增强配置
|
||||||
|
semanticHighlighting: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
colorDecorators: true,
|
||||||
|
// 代码提示和补全
|
||||||
|
suggestOnTriggerCharacters: true,
|
||||||
|
acceptSuggestionOnEnter: 'on',
|
||||||
|
acceptSuggestionOnCommitCharacter: true,
|
||||||
|
quickSuggestions: {
|
||||||
|
other: true,
|
||||||
|
comments: false,
|
||||||
|
strings: false
|
||||||
|
},
|
||||||
|
quickSuggestionsDelay: 100,
|
||||||
|
parameterHints: {
|
||||||
|
enabled: true,
|
||||||
|
cycle: true
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
enabled: true,
|
||||||
|
delay: 300
|
||||||
|
},
|
||||||
|
// 格式化
|
||||||
|
formatOnPaste: true,
|
||||||
|
formatOnType: true,
|
||||||
|
autoIndent: 'advanced',
|
||||||
|
// 括号配对和颜色
|
||||||
|
bracketPairColorization: {
|
||||||
|
enabled: true,
|
||||||
|
independentColorPoolPerBracketType: true
|
||||||
|
},
|
||||||
|
guides: {
|
||||||
|
bracketPairs: true,
|
||||||
|
bracketPairsHorizontal: true,
|
||||||
|
highlightActiveBracketPair: true,
|
||||||
|
indentation: true,
|
||||||
|
highlightActiveIndentation: true
|
||||||
|
},
|
||||||
|
// 代码折叠
|
||||||
|
folding: true,
|
||||||
|
foldingStrategy: 'auto',
|
||||||
|
foldingHighlight: true,
|
||||||
|
foldingImportsByDefault: false,
|
||||||
|
showFoldingControls: 'mouseover',
|
||||||
|
// 其他增强功能
|
||||||
|
links: true,
|
||||||
|
occurrencesHighlight: true,
|
||||||
|
selectionHighlight: true,
|
||||||
|
codeLens: true,
|
||||||
|
lightbulb: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
// 匹配括号
|
||||||
|
matchBrackets: 'always',
|
||||||
|
// 选择相关
|
||||||
|
selectOnLineNumbers: true,
|
||||||
|
selectionClipboard: false,
|
||||||
|
// 渲染相关
|
||||||
|
renderControlCharacters: false,
|
||||||
|
renderFinalNewline: true,
|
||||||
|
renderValidationDecorations: 'on'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 语言映射
|
||||||
|
const getLanguageByExtension = (filename) => {
|
||||||
|
if (!filename) return 'plaintext'
|
||||||
|
|
||||||
|
const ext = filename.split('.').pop()?.toLowerCase()
|
||||||
|
|
||||||
|
const languageMap = {
|
||||||
|
// Web 技术
|
||||||
|
'js': 'javascript',
|
||||||
|
'jsx': 'javascript',
|
||||||
|
'ts': 'typescript',
|
||||||
|
'tsx': 'typescript',
|
||||||
|
'html': 'html',
|
||||||
|
'htm': 'html',
|
||||||
|
'css': 'css',
|
||||||
|
'scss': 'scss',
|
||||||
|
'sass': 'sass',
|
||||||
|
'less': 'less',
|
||||||
|
'vue': 'html', // Vue SFC 使用 HTML 语法高亮
|
||||||
|
'json': 'json',
|
||||||
|
'xml': 'xml',
|
||||||
|
|
||||||
|
// 编程语言
|
||||||
|
'py': 'python',
|
||||||
|
'java': 'java',
|
||||||
|
'c': 'c',
|
||||||
|
'cpp': 'cpp',
|
||||||
|
'cxx': 'cpp',
|
||||||
|
'cc': 'cpp',
|
||||||
|
'h': 'c',
|
||||||
|
'hpp': 'cpp',
|
||||||
|
'cs': 'csharp',
|
||||||
|
'php': 'php',
|
||||||
|
'rb': 'ruby',
|
||||||
|
'go': 'go',
|
||||||
|
'rs': 'rust',
|
||||||
|
'swift': 'swift',
|
||||||
|
'kt': 'kotlin',
|
||||||
|
'scala': 'scala',
|
||||||
|
'r': 'r',
|
||||||
|
'pl': 'perl',
|
||||||
|
'lua': 'lua',
|
||||||
|
|
||||||
|
// Shell 和配置
|
||||||
|
'sh': 'shell',
|
||||||
|
'bash': 'shell',
|
||||||
|
'zsh': 'shell',
|
||||||
|
'fish': 'shell',
|
||||||
|
'ps1': 'powershell',
|
||||||
|
'bat': 'bat',
|
||||||
|
'cmd': 'bat',
|
||||||
|
'dockerfile': 'dockerfile',
|
||||||
|
'yaml': 'yaml',
|
||||||
|
'yml': 'yaml',
|
||||||
|
'toml': 'toml',
|
||||||
|
'ini': 'ini',
|
||||||
|
'conf': 'ini',
|
||||||
|
'cfg': 'ini',
|
||||||
|
|
||||||
|
// 数据格式
|
||||||
|
'sql': 'sql',
|
||||||
|
'md': 'markdown',
|
||||||
|
'markdown': 'markdown',
|
||||||
|
'tex': 'latex',
|
||||||
|
|
||||||
|
// 其他
|
||||||
|
'log': 'plaintext',
|
||||||
|
'txt': 'plaintext'
|
||||||
|
}
|
||||||
|
|
||||||
|
return languageMap[ext] || 'plaintext'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化编辑器
|
||||||
|
const initEditor = async () => {
|
||||||
|
if (!editorContainer.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 设置自定义主题
|
||||||
|
setupCustomThemes()
|
||||||
|
|
||||||
|
// 配置语言特性
|
||||||
|
configureLanguageFeatures()
|
||||||
|
|
||||||
|
// 合并选项
|
||||||
|
const editorOptions = {
|
||||||
|
...defaultOptions,
|
||||||
|
...props.options,
|
||||||
|
value: props.modelValue,
|
||||||
|
language: props.language,
|
||||||
|
theme: props.theme,
|
||||||
|
readOnly: props.readOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建编辑器实例
|
||||||
|
editor = monaco.editor.create(editorContainer.value, editorOptions)
|
||||||
|
|
||||||
|
// 监听内容变化
|
||||||
|
editor.onDidChangeModelContent(() => {
|
||||||
|
const value = editor.getValue()
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听保存快捷键 (Ctrl+S)
|
||||||
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
||||||
|
emit('save')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加更多快捷键
|
||||||
|
setupKeyboardShortcuts()
|
||||||
|
|
||||||
|
console.log('Monaco Editor 初始化成功')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Monaco Editor 初始化失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置语言特性
|
||||||
|
const configureLanguageFeatures = () => {
|
||||||
|
// JavaScript/TypeScript 语言配置
|
||||||
|
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true)
|
||||||
|
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
|
||||||
|
|
||||||
|
// 设置编译选项
|
||||||
|
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||||
|
target: monaco.languages.typescript.ScriptTarget.ES2020,
|
||||||
|
allowNonTsExtensions: true,
|
||||||
|
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||||
|
module: monaco.languages.typescript.ModuleKind.CommonJS,
|
||||||
|
noEmit: true,
|
||||||
|
esModuleInterop: true,
|
||||||
|
jsx: monaco.languages.typescript.JsxEmit.React,
|
||||||
|
reactNamespace: 'React',
|
||||||
|
allowJs: true,
|
||||||
|
typeRoots: ['node_modules/@types']
|
||||||
|
})
|
||||||
|
|
||||||
|
// CSS 语言配置
|
||||||
|
monaco.languages.css.cssDefaults.setOptions({
|
||||||
|
validate: true,
|
||||||
|
lint: {
|
||||||
|
compatibleVendorPrefixes: 'ignore',
|
||||||
|
vendorPrefix: 'warning',
|
||||||
|
duplicateProperties: 'warning',
|
||||||
|
emptyRules: 'warning',
|
||||||
|
importStatement: 'ignore',
|
||||||
|
boxModel: 'ignore',
|
||||||
|
universalSelector: 'ignore',
|
||||||
|
zeroUnits: 'ignore',
|
||||||
|
fontFaceProperties: 'warning',
|
||||||
|
hexColorLength: 'error',
|
||||||
|
argumentsInColorFunction: 'error',
|
||||||
|
unknownProperties: 'warning',
|
||||||
|
ieHack: 'ignore',
|
||||||
|
unknownVendorSpecificProperties: 'ignore',
|
||||||
|
propertyIgnoredDueToDisplay: 'warning',
|
||||||
|
important: 'ignore',
|
||||||
|
float: 'ignore',
|
||||||
|
idSelector: 'ignore'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// HTML 语言配置
|
||||||
|
monaco.languages.html.htmlDefaults.setOptions({
|
||||||
|
format: {
|
||||||
|
tabSize: 2,
|
||||||
|
insertSpaces: true,
|
||||||
|
wrapLineLength: 120,
|
||||||
|
unformatted: 'default"',
|
||||||
|
contentUnformatted: 'pre,code,textarea',
|
||||||
|
indentInnerHtml: false,
|
||||||
|
preserveNewLines: true,
|
||||||
|
maxPreserveNewLines: undefined,
|
||||||
|
indentHandlebars: false,
|
||||||
|
endWithNewline: false,
|
||||||
|
extraLiners: 'head, body, /html',
|
||||||
|
wrapAttributes: 'auto'
|
||||||
|
},
|
||||||
|
suggest: {},
|
||||||
|
validate: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// JSON 语言配置
|
||||||
|
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||||
|
validate: true,
|
||||||
|
allowComments: false,
|
||||||
|
schemas: [],
|
||||||
|
enableSchemaRequest: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置键盘快捷键
|
||||||
|
const setupKeyboardShortcuts = () => {
|
||||||
|
if (!editor) return
|
||||||
|
|
||||||
|
// 格式化代码 (Shift+Alt+F)
|
||||||
|
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyMod.Alt | monaco.KeyCode.KeyF, () => {
|
||||||
|
editor.getAction('editor.action.formatDocument').run()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查找 (Ctrl+F)
|
||||||
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyF, () => {
|
||||||
|
editor.getAction('actions.find').run()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 替换 (Ctrl+H)
|
||||||
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyH, () => {
|
||||||
|
editor.getAction('editor.action.startFindReplaceAction').run()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 转到行 (Ctrl+G)
|
||||||
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyG, () => {
|
||||||
|
editor.getAction('editor.action.gotoLine').run()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 注释/取消注释 (Ctrl+/)
|
||||||
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Slash, () => {
|
||||||
|
editor.getAction('editor.action.commentLine').run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置自定义主题
|
||||||
|
const setupCustomThemes = () => {
|
||||||
|
// 定义自定义深色主题 - 增强版
|
||||||
|
monaco.editor.defineTheme('custom-dark', {
|
||||||
|
base: 'vs-dark',
|
||||||
|
inherit: true,
|
||||||
|
rules: [
|
||||||
|
// 注释
|
||||||
|
{ token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
|
||||||
|
{ token: 'comment.line', foreground: '6A9955', fontStyle: 'italic' },
|
||||||
|
{ token: 'comment.block', foreground: '6A9955', fontStyle: 'italic' },
|
||||||
|
|
||||||
|
// 关键字
|
||||||
|
{ token: 'keyword', foreground: '569CD6', fontStyle: 'bold' },
|
||||||
|
{ token: 'keyword.control', foreground: 'C586C0', fontStyle: 'bold' },
|
||||||
|
{ token: 'keyword.operator', foreground: '569CD6' },
|
||||||
|
{ token: 'keyword.other', foreground: '569CD6' },
|
||||||
|
|
||||||
|
// 字符串
|
||||||
|
{ token: 'string', foreground: 'CE9178' },
|
||||||
|
{ token: 'string.quoted', foreground: 'CE9178' },
|
||||||
|
{ token: 'string.template', foreground: 'CE9178' },
|
||||||
|
{ token: 'string.regexp', foreground: 'D16969' },
|
||||||
|
|
||||||
|
// 数字
|
||||||
|
{ token: 'number', foreground: 'B5CEA8' },
|
||||||
|
{ token: 'number.hex', foreground: 'B5CEA8' },
|
||||||
|
{ token: 'number.octal', foreground: 'B5CEA8' },
|
||||||
|
{ token: 'number.binary', foreground: 'B5CEA8' },
|
||||||
|
|
||||||
|
// 变量和标识符
|
||||||
|
{ token: 'variable', foreground: '9CDCFE' },
|
||||||
|
{ token: 'variable.name', foreground: '9CDCFE' },
|
||||||
|
{ token: 'variable.parameter', foreground: '9CDCFE' },
|
||||||
|
{ token: 'variable.other', foreground: '9CDCFE' },
|
||||||
|
|
||||||
|
// 函数和方法
|
||||||
|
{ token: 'function', foreground: 'DCDCAA' },
|
||||||
|
{ token: 'function.name', foreground: 'DCDCAA' },
|
||||||
|
{ token: 'method', foreground: 'DCDCAA' },
|
||||||
|
{ token: 'method.name', foreground: 'DCDCAA' },
|
||||||
|
|
||||||
|
// 类型和类
|
||||||
|
{ token: 'type', foreground: '4EC9B0' },
|
||||||
|
{ token: 'type.name', foreground: '4EC9B0' },
|
||||||
|
{ token: 'class', foreground: '4EC9B0' },
|
||||||
|
{ token: 'class.name', foreground: '4EC9B0' },
|
||||||
|
{ token: 'interface', foreground: 'B8D7A3' },
|
||||||
|
{ token: 'enum', foreground: 'B8D7A3' },
|
||||||
|
{ token: 'struct', foreground: '86C691' },
|
||||||
|
|
||||||
|
// 操作符和标点
|
||||||
|
{ token: 'operator', foreground: 'D4D4D4' },
|
||||||
|
{ token: 'delimiter', foreground: 'D4D4D4' },
|
||||||
|
{ token: 'punctuation', foreground: 'D4D4D4' },
|
||||||
|
|
||||||
|
// 特殊标记
|
||||||
|
{ token: 'tag', foreground: '569CD6' },
|
||||||
|
{ token: 'tag.name', foreground: '569CD6' },
|
||||||
|
{ token: 'attribute', foreground: '9CDCFE' },
|
||||||
|
{ token: 'attribute.name', foreground: '9CDCFE' },
|
||||||
|
{ token: 'attribute.value', foreground: 'CE9178' },
|
||||||
|
|
||||||
|
// 装饰器和宏
|
||||||
|
{ token: 'decorator', foreground: 'C586C0' },
|
||||||
|
{ token: 'macro', foreground: 'C586C0' },
|
||||||
|
|
||||||
|
// JavaScript/TypeScript 特定
|
||||||
|
{ token: 'support.type', foreground: '4EC9B0' },
|
||||||
|
{ token: 'support.class', foreground: '4EC9B0' },
|
||||||
|
{ token: 'support.function', foreground: 'DCDCAA' },
|
||||||
|
{ token: 'support.variable', foreground: '4FC1FF' },
|
||||||
|
|
||||||
|
// CSS 特定
|
||||||
|
{ token: 'property', foreground: '9CDCFE' },
|
||||||
|
{ token: 'property.name', foreground: '9CDCFE' },
|
||||||
|
{ token: 'property.value', foreground: 'CE9178' },
|
||||||
|
{ token: 'selector', foreground: 'D7BA7D' },
|
||||||
|
|
||||||
|
// JSON 特定
|
||||||
|
{ token: 'key', foreground: '9CDCFE' },
|
||||||
|
{ token: 'value', foreground: 'CE9178' }
|
||||||
|
],
|
||||||
|
colors: {
|
||||||
|
'editor.background': '#1E1E1E',
|
||||||
|
'editor.foreground': '#D4D4D4',
|
||||||
|
'editorLineNumber.foreground': '#858585',
|
||||||
|
'editorLineNumber.activeForeground': '#C6C6C6',
|
||||||
|
'editor.selectionBackground': '#264F78',
|
||||||
|
'editor.inactiveSelectionBackground': '#3A3D41',
|
||||||
|
'editorCursor.foreground': '#AEAFAD',
|
||||||
|
'editor.findMatchBackground': '#515C6A',
|
||||||
|
'editor.findMatchHighlightBackground': '#EA5C0055',
|
||||||
|
'editor.linkedEditingBackground': '#FF00007A',
|
||||||
|
'editorBracketMatch.background': '#0064001A',
|
||||||
|
'editorBracketMatch.border': '#888888'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义自定义浅色主题 - 增强版
|
||||||
|
monaco.editor.defineTheme('custom-light', {
|
||||||
|
base: 'vs',
|
||||||
|
inherit: true,
|
||||||
|
rules: [
|
||||||
|
// 注释
|
||||||
|
{ token: 'comment', foreground: '008000', fontStyle: 'italic' },
|
||||||
|
{ token: 'comment.line', foreground: '008000', fontStyle: 'italic' },
|
||||||
|
{ token: 'comment.block', foreground: '008000', fontStyle: 'italic' },
|
||||||
|
|
||||||
|
// 关键字
|
||||||
|
{ token: 'keyword', foreground: '0000FF', fontStyle: 'bold' },
|
||||||
|
{ token: 'keyword.control', foreground: 'AF00DB', fontStyle: 'bold' },
|
||||||
|
{ token: 'keyword.operator', foreground: '0000FF' },
|
||||||
|
{ token: 'keyword.other', foreground: '0000FF' },
|
||||||
|
|
||||||
|
// 字符串
|
||||||
|
{ token: 'string', foreground: 'A31515' },
|
||||||
|
{ token: 'string.quoted', foreground: 'A31515' },
|
||||||
|
{ token: 'string.template', foreground: 'A31515' },
|
||||||
|
{ token: 'string.regexp', foreground: 'D16969' },
|
||||||
|
|
||||||
|
// 数字
|
||||||
|
{ token: 'number', foreground: '098658' },
|
||||||
|
{ token: 'number.hex', foreground: '098658' },
|
||||||
|
{ token: 'number.octal', foreground: '098658' },
|
||||||
|
{ token: 'number.binary', foreground: '098658' },
|
||||||
|
|
||||||
|
// 变量和标识符
|
||||||
|
{ token: 'variable', foreground: '001080' },
|
||||||
|
{ token: 'variable.name', foreground: '001080' },
|
||||||
|
{ token: 'variable.parameter', foreground: '001080' },
|
||||||
|
{ token: 'variable.other', foreground: '001080' },
|
||||||
|
|
||||||
|
// 函数和方法
|
||||||
|
{ token: 'function', foreground: '795E26' },
|
||||||
|
{ token: 'function.name', foreground: '795E26' },
|
||||||
|
{ token: 'method', foreground: '795E26' },
|
||||||
|
{ token: 'method.name', foreground: '795E26' },
|
||||||
|
|
||||||
|
// 类型和类
|
||||||
|
{ token: 'type', foreground: '267F99' },
|
||||||
|
{ token: 'type.name', foreground: '267F99' },
|
||||||
|
{ token: 'class', foreground: '267F99' },
|
||||||
|
{ token: 'class.name', foreground: '267F99' },
|
||||||
|
{ token: 'interface', foreground: '007ACC' },
|
||||||
|
{ token: 'enum', foreground: '007ACC' },
|
||||||
|
{ token: 'struct', foreground: '267F99' },
|
||||||
|
|
||||||
|
// 操作符和标点
|
||||||
|
{ token: 'operator', foreground: '000000' },
|
||||||
|
{ token: 'delimiter', foreground: '000000' },
|
||||||
|
{ token: 'punctuation', foreground: '000000' },
|
||||||
|
|
||||||
|
// 特殊标记
|
||||||
|
{ token: 'tag', foreground: '800000' },
|
||||||
|
{ token: 'tag.name', foreground: '800000' },
|
||||||
|
{ token: 'attribute', foreground: 'FF0000' },
|
||||||
|
{ token: 'attribute.name', foreground: 'FF0000' },
|
||||||
|
{ token: 'attribute.value', foreground: '0000FF' },
|
||||||
|
|
||||||
|
// 装饰器和宏
|
||||||
|
{ token: 'decorator', foreground: 'AF00DB' },
|
||||||
|
{ token: 'macro', foreground: 'AF00DB' },
|
||||||
|
|
||||||
|
// JavaScript/TypeScript 特定
|
||||||
|
{ token: 'support.type', foreground: '267F99' },
|
||||||
|
{ token: 'support.class', foreground: '267F99' },
|
||||||
|
{ token: 'support.function', foreground: '795E26' },
|
||||||
|
{ token: 'support.variable', foreground: '0070C1' },
|
||||||
|
|
||||||
|
// CSS 特定
|
||||||
|
{ token: 'property', foreground: 'FF0000' },
|
||||||
|
{ token: 'property.name', foreground: 'FF0000' },
|
||||||
|
{ token: 'property.value', foreground: '0451A5' },
|
||||||
|
{ token: 'selector', foreground: '800000' },
|
||||||
|
|
||||||
|
// JSON 特定
|
||||||
|
{ token: 'key', foreground: '0451A5' },
|
||||||
|
{ token: 'value', foreground: 'A31515' }
|
||||||
|
],
|
||||||
|
colors: {
|
||||||
|
'editor.background': '#FFFFFF',
|
||||||
|
'editor.foreground': '#000000',
|
||||||
|
'editorLineNumber.foreground': '#237893',
|
||||||
|
'editor.selectionBackground': '#ADD6FF',
|
||||||
|
'editor.inactiveSelectionBackground': '#E5EBF1',
|
||||||
|
'editorCursor.foreground': '#000000',
|
||||||
|
'editor.findMatchBackground': '#A8AC94',
|
||||||
|
'editor.findMatchHighlightBackground': '#EA5C0055'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新编辑器值
|
||||||
|
const updateValue = (newValue) => {
|
||||||
|
if (editor && editor.getValue() !== newValue) {
|
||||||
|
editor.setValue(newValue || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新编辑器语言
|
||||||
|
const updateLanguage = (newLanguage) => {
|
||||||
|
if (editor) {
|
||||||
|
const model = editor.getModel()
|
||||||
|
if (model) {
|
||||||
|
monaco.editor.setModelLanguage(model, newLanguage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新编辑器主题
|
||||||
|
const updateTheme = (newTheme) => {
|
||||||
|
if (editor) {
|
||||||
|
monaco.editor.setTheme(newTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调整编辑器大小
|
||||||
|
const resizeEditor = () => {
|
||||||
|
if (editor) {
|
||||||
|
nextTick(() => {
|
||||||
|
editor.layout()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取编辑器实例
|
||||||
|
const getEditor = () => editor
|
||||||
|
|
||||||
|
// 聚焦编辑器
|
||||||
|
const focus = () => {
|
||||||
|
if (editor) {
|
||||||
|
editor.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置编辑器内容
|
||||||
|
const setValue = (value) => {
|
||||||
|
if (editor) {
|
||||||
|
editor.setValue(value || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取编辑器内容
|
||||||
|
const getValue = () => {
|
||||||
|
return editor ? editor.getValue() : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听属性变化
|
||||||
|
watch(() => props.modelValue, updateValue)
|
||||||
|
watch(() => props.language, updateLanguage)
|
||||||
|
watch(() => props.theme, updateTheme)
|
||||||
|
watch(() => props.height, resizeEditor)
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initEditor()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (editor) {
|
||||||
|
editor.dispose()
|
||||||
|
editor = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
getEditor,
|
||||||
|
focus,
|
||||||
|
setValue,
|
||||||
|
getValue,
|
||||||
|
resizeEditor,
|
||||||
|
getLanguageByExtension
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.monaco-editor-container {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-editor-container:focus-within {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -202,6 +202,32 @@ const routes = [
|
|||||||
title: '虚拟机详情',
|
title: '虚拟机详情',
|
||||||
hidden: true
|
hidden: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 容器详情页面路由
|
||||||
|
{
|
||||||
|
path: 'servers/container',
|
||||||
|
name: 'ContainerDetail',
|
||||||
|
component: () => import('../views/acs/nodes/containDetail.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '容器详情',
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
path:'servers/container/console',
|
||||||
|
name:'ContainerConsole',
|
||||||
|
component:()=>import('../views/acs/nodes/containerConsole.vue'),
|
||||||
|
meta:{
|
||||||
|
title:'终端容器',
|
||||||
|
hidden:true
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
path:'servers/container/files',
|
||||||
|
name:'ContainerFiles',
|
||||||
|
component:()=>import('../views/acs/nodes/containFile.vue'),
|
||||||
|
meta:{
|
||||||
|
title:'容器文件管理',
|
||||||
|
hidden:true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {http2} from "@/utils/request.js";
|
||||||
|
export const getFileList = (data) => {
|
||||||
|
return http2.get(`/v1/file/list?container_id=${data.container_id}&path=${data.path}`)
|
||||||
|
}
|
||||||
|
/** 读取文件内容 */
|
||||||
|
export const readFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/read`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/*删除文件或文件夹 */
|
||||||
|
export const deleteFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/delete`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/*写入文件 */
|
||||||
|
export const writeFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/write`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/*创建文件夹 */
|
||||||
|
export const createFolder = (data) => {
|
||||||
|
return http2.post(`/v1/file/mkdir`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**上传文件 */
|
||||||
|
export const uploadFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/upload_file`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**下载文件链接 */
|
||||||
|
export const downloadFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/get_down_link`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**压缩文件 */
|
||||||
|
export const compressFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/zip_file`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**解压文件 */
|
||||||
|
export const decompressFile = (data) => {
|
||||||
|
return http2.post(`/v1/file/unzip_file`,data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
+12
-1
@@ -53,6 +53,16 @@ export const selectServerPlan = data => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**删除容器网络 */
|
||||||
|
export const deleteContainerNetwork = data => {
|
||||||
|
return http2.post("/v1/user/container/delete_connect", data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**修改套餐信息 */
|
/**修改套餐信息 */
|
||||||
export const editServerPlan = data => {
|
export const editServerPlan = data => {
|
||||||
return http2.post("/v1/admin/container_plan/update_server_plan", data, {
|
return http2.post("/v1/admin/container_plan/update_server_plan", data, {
|
||||||
@@ -275,7 +285,7 @@ export const getInstanceConsole = data => {
|
|||||||
};
|
};
|
||||||
/**查询容器所有卷信息 */
|
/**查询容器所有卷信息 */
|
||||||
export const getVolumeList = data => {
|
export const getVolumeList = data => {
|
||||||
return http2.get(`/v1/admin/volume/get_volume_list?container_id=${data}`);
|
return http2.get(`/v1/admin/volume/get_volume_list?instance_id=${data.instance_id}&page=${data.page}&count=${data.count}`);
|
||||||
};
|
};
|
||||||
/**查询虚拟机所有卷信息 */
|
/**查询虚拟机所有卷信息 */
|
||||||
export const getInstanceVolumeList = data => {
|
export const getInstanceVolumeList = data => {
|
||||||
@@ -307,6 +317,7 @@ export const deleteVolume = data => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**获取容器网络信息 */
|
/**获取容器网络信息 */
|
||||||
export const getNetworkList = data => {
|
export const getNetworkList = data => {
|
||||||
return http2.get(
|
return http2.get(
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import type { VNode } from "vue";
|
||||||
|
import { type MessageHandler, ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const isFunction = (val: unknown): val is Function => typeof val === 'function';
|
||||||
|
|
||||||
|
type messageStyle = "el" | "antd";
|
||||||
|
type messageTypes = "info" | "success" | "warning" | "error";
|
||||||
|
|
||||||
|
interface MessageParams {
|
||||||
|
/** 消息类型,可选 `info` 、`success` 、`warning` 、`error` ,默认 `info` */
|
||||||
|
type?: messageTypes;
|
||||||
|
/** 自定义图标,该属性会覆盖 `type` 的图标 */
|
||||||
|
icon?: any;
|
||||||
|
/** 是否将 `message` 属性作为 `HTML` 片段处理,默认 `false` */
|
||||||
|
dangerouslyUseHTMLString?: boolean;
|
||||||
|
/** 消息风格,可选 `el` 、`antd` ,默认 `antd` */
|
||||||
|
customClass?: messageStyle;
|
||||||
|
/** 显示时间,单位为毫秒。设为 `0` 则不会自动关闭,`element-plus` 默认是 `3000` ,平台改成默认 `2000` */
|
||||||
|
duration?: number;
|
||||||
|
/** 是否显示关闭按钮,默认值 `false` */
|
||||||
|
showClose?: boolean;
|
||||||
|
|
||||||
|
/** `Message` 距离窗口顶部的偏移量,默认 `20` */
|
||||||
|
offset?: number;
|
||||||
|
/** 设置组件的根元素,默认 `document.body` */
|
||||||
|
appendTo?: string | HTMLElement;
|
||||||
|
/** 合并内容相同的消息,不支持 `VNode` 类型的消息,默认值 `false` */
|
||||||
|
grouping?: boolean;
|
||||||
|
/** 关闭时的回调函数, 参数为被关闭的 `message` 实例 */
|
||||||
|
onClose?: Function | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用法非常简单,参考 src/views/components/message/index.vue 文件 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Message` 消息提示函数
|
||||||
|
*/
|
||||||
|
const message = (
|
||||||
|
message: string | VNode | (() => VNode),
|
||||||
|
params?: MessageParams
|
||||||
|
): MessageHandler => {
|
||||||
|
if (!params) {
|
||||||
|
return ElMessage({
|
||||||
|
message,
|
||||||
|
customClass: "pure-message"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
icon,
|
||||||
|
type = "info",
|
||||||
|
dangerouslyUseHTMLString = false,
|
||||||
|
customClass = "antd",
|
||||||
|
duration = 2000,
|
||||||
|
showClose = false,
|
||||||
|
|
||||||
|
offset = 20,
|
||||||
|
appendTo = document.body,
|
||||||
|
grouping = false,
|
||||||
|
onClose
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
return ElMessage({
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
icon,
|
||||||
|
dangerouslyUseHTMLString,
|
||||||
|
duration,
|
||||||
|
showClose,
|
||||||
|
offset,
|
||||||
|
appendTo,
|
||||||
|
grouping,
|
||||||
|
// 全局搜 pure-message 即可知道该类的样式位置
|
||||||
|
customClass: customClass === "antd" ? "pure-message" : "",
|
||||||
|
onClose: () => (isFunction(onClose) ? onClose() : null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭所有 `Message` 消息提示函数
|
||||||
|
*/
|
||||||
|
const closeAllMessage = (): void => ElMessage.closeAll();
|
||||||
|
|
||||||
|
export { message, closeAllMessage };
|
||||||
@@ -106,7 +106,7 @@ class Request {
|
|||||||
// 创建默认实例
|
// 创建默认实例
|
||||||
const request = new Request({
|
const request = new Request({
|
||||||
baseURL: baseUrl,
|
baseURL: baseUrl,
|
||||||
timeout: 30000,
|
timeout: 50000,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const FileName = (data) =>{
|
||||||
|
let name = data.split("/").pop()
|
||||||
|
return name
|
||||||
|
}
|
||||||
+2270
File diff suppressed because it is too large
Load Diff
+4958
File diff suppressed because it is too large
Load Diff
@@ -195,6 +195,12 @@
|
|||||||
<el-form-item label="展示名称" prop="show_name">
|
<el-form-item label="展示名称" prop="show_name">
|
||||||
<el-input v-model="form.show_name" placeholder="请输入展示名称" />
|
<el-input v-model="form.show_name" placeholder="请输入展示名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="分类ID" prop="class_id">
|
||||||
|
<el-input v-model="form.class_id" placeholder="请输入分类ID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="分类名称" prop="class_name">
|
||||||
|
<el-input v-model="form.class_name" placeholder="请输入分类名称" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="图标">
|
<el-form-item label="图标">
|
||||||
<div class="image-icon-upload">
|
<div class="image-icon-upload">
|
||||||
<img v-if="form.image_ico" :src="mainUrl + form.image_ico" class="preview-icon" />
|
<img v-if="form.image_ico" :src="mainUrl + form.image_ico" class="preview-icon" />
|
||||||
@@ -441,7 +447,9 @@ const form = reactive({
|
|||||||
image_ico: '',
|
image_ico: '',
|
||||||
tag: '',
|
tag: '',
|
||||||
server_id: '',
|
server_id: '',
|
||||||
env: ''
|
env: '',
|
||||||
|
class_id: '',
|
||||||
|
class_name: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
@@ -449,7 +457,9 @@ const rules = {
|
|||||||
show_name: [{ required: true, message: '请输入展示名称', trigger: 'blur' }],
|
show_name: [{ required: true, message: '请输入展示名称', trigger: 'blur' }],
|
||||||
tag: [{ required: true, message: '请输入版本号', trigger: 'blur' }],
|
tag: [{ required: true, message: '请输入版本号', trigger: 'blur' }],
|
||||||
plan_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
plan_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
||||||
description: [{ required: true, message: '请输入镜像描述', trigger: 'blur' }]
|
description: [{ required: true, message: '请输入镜像描述', trigger: 'blur' }],
|
||||||
|
class_id: [{ required: false, message: '请输入分类ID', trigger: 'blur' }],
|
||||||
|
class_name: [{ required: false, message: '请输入分类名称', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
const netform = reactive({
|
const netform = reactive({
|
||||||
|
|||||||
@@ -141,6 +141,11 @@
|
|||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
type="primary" @click="handleOpen">
|
||||||
|
<el-icon><VideoPlay /></el-icon>开通
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
:disabled="vmInfo.state == 2 || vmInfo.state == 0 || vmInfo.state == 1 || vmInfo.state == 4 || vmInfo.state == 5 || vmInfo.state == 6"
|
:disabled="vmInfo.state == 2 || vmInfo.state == 0 || vmInfo.state == 1 || vmInfo.state == 4 || vmInfo.state == 5 || vmInfo.state == 6"
|
||||||
@@ -155,6 +160,20 @@
|
|||||||
>
|
>
|
||||||
<el-icon><VideoPause /></el-icon>关机
|
<el-icon><VideoPause /></el-icon>关机
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="info"
|
||||||
|
:disabled="vmInfo.state != 2"
|
||||||
|
@click="handlePause"
|
||||||
|
>
|
||||||
|
<el-icon><VideoPause /></el-icon>暂停
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
:disabled="vmInfo.state != 7"
|
||||||
|
@click="handleUnpause"
|
||||||
|
>
|
||||||
|
<el-icon><VideoPlay /></el-icon>恢复
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="info"
|
type="info"
|
||||||
:disabled="vmInfo.state != 2"
|
:disabled="vmInfo.state != 2"
|
||||||
@@ -189,13 +208,20 @@
|
|||||||
>
|
>
|
||||||
<el-icon><Warning /></el-icon>退出救援模式
|
<el-icon><Warning /></el-icon>退出救援模式
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
:disabled="vmInfo.state == 0 || vmInfo.state == 1 || vmInfo.state == 4 || vmInfo.state == 5 || vmInfo.state == 6"
|
||||||
|
@click="handleDelete"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>删除虚拟机
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 -->
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<el-tabs type="border-card" class="main-tabs">
|
<el-tabs type="border-card" class="main-tabs" v-model="activeTabName" @tab-click="handleTabClick">
|
||||||
<!-- 虚拟机操作日志 -->
|
<!-- 虚拟机操作日志 -->
|
||||||
<el-tab-pane label="虚拟机操作日志">
|
<el-tab-pane label="虚拟机操作日志" name="0">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">操作日志</h3>
|
<h3 class="tab-title">操作日志</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -229,7 +255,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 实例监控 -->
|
<!-- 实例监控 -->
|
||||||
<el-tab-pane label="实例监控">
|
<el-tab-pane label="实例监控" name="1">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">实例监控</h3>
|
<h3 class="tab-title">实例监控</h3>
|
||||||
<div class="date-filter">
|
<div class="date-filter">
|
||||||
@@ -294,7 +320,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 虚拟机网络管理 -->
|
<!-- 虚拟机网络管理 -->
|
||||||
<el-tab-pane label="虚拟机网络管理">
|
<el-tab-pane label="虚拟机网络管理" name="2">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">网络管理</h3>
|
<h3 class="tab-title">网络管理</h3>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -360,7 +386,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 端口管理 -->
|
<!-- 端口管理 -->
|
||||||
<el-tab-pane label="端口管理">
|
<el-tab-pane label="端口管理" name="3">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">端口列表</h3>
|
<h3 class="tab-title">端口列表</h3>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -414,7 +440,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 快照列表 -->
|
<!-- 快照列表 -->
|
||||||
<el-tab-pane label="快照列表">
|
<el-tab-pane label="快照列表" name="4">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">快照管理</h3>
|
<h3 class="tab-title">快照管理</h3>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -463,7 +489,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 数据卷信息 -->
|
<!-- 数据卷信息 -->
|
||||||
<el-tab-pane label="数据卷信息">
|
<el-tab-pane label="数据卷信息" name="5">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">数据卷列表</h3>
|
<h3 class="tab-title">数据卷列表</h3>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -774,7 +800,11 @@ import {
|
|||||||
addVolume,
|
addVolume,
|
||||||
deleteVolume,
|
deleteVolume,
|
||||||
updateVolume,
|
updateVolume,
|
||||||
getInstanceList
|
getInstanceList,
|
||||||
|
openInstance,
|
||||||
|
pauseInstance,
|
||||||
|
unpauseInstance,
|
||||||
|
deleteInstance
|
||||||
} from '@/utils/acs/server';
|
} from '@/utils/acs/server';
|
||||||
import {
|
import {
|
||||||
Mirrorinfo,
|
Mirrorinfo,
|
||||||
@@ -798,6 +828,14 @@ const route = useRoute();
|
|||||||
const vmInfo = ref({});
|
const vmInfo = ref({});
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 标签页相关
|
||||||
|
const activeTabName = ref('0'); // 默认选中第一个标签
|
||||||
|
|
||||||
|
// 处理标签页点击
|
||||||
|
const handleTabClick = (tab) => {
|
||||||
|
localStorage.setItem('vmDetailActiveTab', tab.index);
|
||||||
|
};
|
||||||
|
|
||||||
// 端口管理
|
// 端口管理
|
||||||
const portsList = ref([]);
|
const portsList = ref([]);
|
||||||
const portsLoading = ref(false);
|
const portsLoading = ref(false);
|
||||||
@@ -1004,6 +1042,17 @@ const reinstallRules = {
|
|||||||
const reinstalling = ref(false);
|
const reinstalling = ref(false);
|
||||||
const imagesList = ref([]);
|
const imagesList = ref([]);
|
||||||
|
|
||||||
|
//开通虚拟机
|
||||||
|
const handleOpen = async () => {
|
||||||
|
const res = await openInstance(route.query.instance_id)
|
||||||
|
console.log("开通虚拟机",res)
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success(res.data.msg);
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取镜像列表
|
// 获取镜像列表
|
||||||
const fetchImagesList = async () => {
|
const fetchImagesList = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -1111,6 +1160,12 @@ const fetchDataVolumesList = async () => {
|
|||||||
// 初始化数据
|
// 初始化数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (route.query.instance_id) {
|
if (route.query.instance_id) {
|
||||||
|
// 恢复上次选中的标签页
|
||||||
|
const savedTab = localStorage.getItem('vmDetailActiveTab');
|
||||||
|
if (savedTab) {
|
||||||
|
activeTabName.value = savedTab;
|
||||||
|
}
|
||||||
|
|
||||||
fetchVmInfo();
|
fetchVmInfo();
|
||||||
fetchPortsList();
|
fetchPortsList();
|
||||||
fetchLogsList();
|
fetchLogsList();
|
||||||
@@ -1424,6 +1479,83 @@ const handleExitRescue = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 暂停虚拟机
|
||||||
|
const handlePause = async () => {
|
||||||
|
try {
|
||||||
|
ElMessageBox.confirm('确定要暂停该虚拟机吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
const res = await pauseInstance(formData, route.query.instance_id);
|
||||||
|
if (res && res.data && res.data.code === 200) {
|
||||||
|
ElMessage.success('暂停指令已发送');
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchVmInfo();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
ElMessage.error('暂停失败: ' + (res.data.message || '未知错误'));
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('暂停虚拟机出错:', error);
|
||||||
|
ElMessage.error('暂停虚拟机出错');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 恢复虚拟机
|
||||||
|
const handleUnpause = async () => {
|
||||||
|
try {
|
||||||
|
ElMessageBox.confirm('确定要恢复该虚拟机吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await unpauseInstance(route.query.instance_id);
|
||||||
|
if (res && res.data && res.data.code === 200) {
|
||||||
|
ElMessage.success('恢复指令已发送');
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchVmInfo();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
ElMessage.error('恢复失败: ' + (res.data.message || '未知错误'));
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('恢复虚拟机出错:', error);
|
||||||
|
ElMessage.error('恢复虚拟机出错');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除虚拟机
|
||||||
|
const handleDelete = async () => {
|
||||||
|
try {
|
||||||
|
ElMessageBox.confirm('确定要删除该虚拟机吗?此操作不可恢复!', '警告', {
|
||||||
|
confirmButtonText: '确定删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'error',
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<div style="color: red; font-weight: bold;">⚠️ 警告:删除虚拟机将永久删除所有数据,此操作不可恢复!</div>'
|
||||||
|
}).then(async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
const res = await deleteInstance(route.query.instance_id, formData);
|
||||||
|
if (res && res.data && res.data.code === 200) {
|
||||||
|
ElMessage.success('虚拟机已删除');
|
||||||
|
// 删除成功后返回虚拟机列表页面
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/acs/nodes');
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
ElMessage.error('删除失败: ' + (res.data.message || '未知错误'));
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除虚拟机出错:', error);
|
||||||
|
ElMessage.error('删除虚拟机出错');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function statusMap(data) {
|
function statusMap(data) {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
0: "未支付",
|
0: "未支付",
|
||||||
@@ -1432,7 +1564,8 @@ function statusMap(data) {
|
|||||||
3: "关机",
|
3: "关机",
|
||||||
4: "重装中",
|
4: "重装中",
|
||||||
5: "正在创建快照",
|
5: "正在创建快照",
|
||||||
6: "正在恢复快照"
|
6: "正在恢复快照",
|
||||||
|
7: "已暂停"
|
||||||
};
|
};
|
||||||
return statusMap[data] || "未知状态";
|
return statusMap[data] || "未知状态";
|
||||||
}
|
}
|
||||||
@@ -1454,7 +1587,8 @@ function statusColor(data) {
|
|||||||
3: "secondary", // 关机
|
3: "secondary", // 关机
|
||||||
4: "info", // 重装中
|
4: "info", // 重装中
|
||||||
5: "primary", // 正在创建快照
|
5: "primary", // 正在创建快照
|
||||||
6: "info" // 正在恢复快照
|
6: "info", // 正在恢复快照
|
||||||
|
7: "warning" // 已暂停
|
||||||
};
|
};
|
||||||
return statusMap[data] || "default"; // 默认颜色类名
|
return statusMap[data] || "default"; // 默认颜色类名
|
||||||
}
|
}
|
||||||
@@ -1571,6 +1705,8 @@ const getStatusType = (state) => {
|
|||||||
return 'success';
|
return 'success';
|
||||||
} else if (state == 4 || state == 5 || state == 6) {
|
} else if (state == 4 || state == 5 || state == 6) {
|
||||||
return 'warning';
|
return 'warning';
|
||||||
|
} else if (state == 7) {
|
||||||
|
return 'info'; // 暂停状态
|
||||||
} else {
|
} else {
|
||||||
return 'danger';
|
return 'danger';
|
||||||
}
|
}
|
||||||
@@ -1593,6 +1729,8 @@ const getStatusText = (state) => {
|
|||||||
return '创建快照中';
|
return '创建快照中';
|
||||||
case 6:
|
case 6:
|
||||||
return '恢复快照中';
|
return '恢复快照中';
|
||||||
|
case 7:
|
||||||
|
return '已暂停';
|
||||||
default:
|
default:
|
||||||
return '未知状态';
|
return '未知状态';
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div id="container-terminal"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
body{
|
||||||
|
padding: 0%;
|
||||||
|
margin:5px;
|
||||||
|
background-color: #000;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#container-terminal{
|
||||||
|
width:100%;
|
||||||
|
height:85%;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Terminal } from 'xterm'
|
||||||
|
import 'xterm/css/xterm.css'
|
||||||
|
import {useRoute,useRouter} from "vue-router"
|
||||||
|
import {message} from "../../../utils/message"
|
||||||
|
import {onMounted,onUnmounted} from "vue"
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
onMounted(() =>{
|
||||||
|
checkProtocol()
|
||||||
|
stopHeartBeat()
|
||||||
|
})
|
||||||
|
|
||||||
|
let heartbeatTimerId = null;
|
||||||
|
function startHeartBeat(data:any){
|
||||||
|
heartbeatTimerId = setInterval(() => {
|
||||||
|
data.send("")
|
||||||
|
},10000);
|
||||||
|
}
|
||||||
|
function stopHeartBeat(){
|
||||||
|
clearInterval(heartbeatTimerId)
|
||||||
|
}
|
||||||
|
//判断协议
|
||||||
|
function checkProtocol(){
|
||||||
|
if(window.location.protocol === "https:" &&
|
||||||
|
String(route.query.server_addr).split(":")[0]!== "ws"){
|
||||||
|
console.log("当前页面使用的是https协议")
|
||||||
|
router.back();
|
||||||
|
}else{
|
||||||
|
console.log("当前页面使用的是HTTP协议")
|
||||||
|
function resizeTerminal(term:any){
|
||||||
|
const container = document.getElementById("container-terminal")
|
||||||
|
if(!container) {
|
||||||
|
console.error("Container terminal not found")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const style = window.getComputedStyle(container)
|
||||||
|
const charWidth = parseInt(style.getPropertyValue("--xterm-char-width"),10) || 10;
|
||||||
|
const charHeight = parseInt(style.getPropertyValue("--xterm-char-height"),10) || 20;
|
||||||
|
const width = container.offsetWidth;
|
||||||
|
const height = container.offsetHeight;
|
||||||
|
const cols = Math.floor(width /charWidth);
|
||||||
|
const rows = Math.floor(height / charHeight);
|
||||||
|
term.resize(cols,rows);
|
||||||
|
}
|
||||||
|
function getQueryVal(variable:any){
|
||||||
|
const query = window.location.search.substring(1);
|
||||||
|
const vars = query.split("&");
|
||||||
|
for(let i = 0;i< vars.length;i++){
|
||||||
|
const pair = vars[i].split("=");
|
||||||
|
if(pair[0] === variable){
|
||||||
|
return pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let token = route.query.token;
|
||||||
|
if(!token){
|
||||||
|
throw new Error("token not found")
|
||||||
|
}
|
||||||
|
let server_addr = route.query.server_addr
|
||||||
|
if(!server_addr){
|
||||||
|
throw new Error("server_addr not found")
|
||||||
|
}
|
||||||
|
let command = getQueryVal("cmd")
|
||||||
|
if(!command) {
|
||||||
|
command = "/bin/bash"
|
||||||
|
}
|
||||||
|
let websocket = new WebSocket(
|
||||||
|
"ws://" + server_addr + "/console/" + "exec/" + token + "," + window.btoa(command)
|
||||||
|
)
|
||||||
|
websocket.onerror = function(event:any){
|
||||||
|
console.error("WebSocket error:",event)
|
||||||
|
message("网络波动",{type:"error"});
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
websocket.onopen = function(){
|
||||||
|
let term = new Terminal({
|
||||||
|
cols: 300,
|
||||||
|
rows:30,
|
||||||
|
cursorBlink:true
|
||||||
|
})
|
||||||
|
console.log("打开终端")
|
||||||
|
startHeartBeat(websocket)
|
||||||
|
term.onData(function(data:any){
|
||||||
|
websocket.send(data)
|
||||||
|
})
|
||||||
|
term.onTitleChange(function(title:any){
|
||||||
|
document.title = title
|
||||||
|
})
|
||||||
|
term.open(document.getElementById("container-terminal")!);
|
||||||
|
resizeTerminal(term);
|
||||||
|
window.addEventListener("resize",()=>{
|
||||||
|
resizeTerminal(term)
|
||||||
|
})
|
||||||
|
websocket.onmessage = function (evt) {
|
||||||
|
term.write(evt.data)
|
||||||
|
console.log("发送消息",evt)
|
||||||
|
}
|
||||||
|
websocket.onclose = function(){
|
||||||
|
term.write("session terminated");
|
||||||
|
term.dispose()
|
||||||
|
}
|
||||||
|
websocket.onerror = function (evt){
|
||||||
|
if(evt) {
|
||||||
|
message("网络波动",{type:"error"})
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -217,30 +217,36 @@
|
|||||||
<div class="time-info">{{ scope.row.become_time }}</div>
|
<div class="time-info">{{ scope.row.become_time }}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="规格" prop="plan_id" width="80" />
|
<el-table-column label="规格" prop="plan_name" width="80" />
|
||||||
<el-table-column label="用户ID" prop="user_id" width="80" />
|
<el-table-column label="用户ID" prop="user_id" width="80" />
|
||||||
<el-table-column label="状态" width="100" align="center">
|
<el-table-column label="状态" width="100" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag
|
<el-tag
|
||||||
:type="scope.row.container_state == 0 || scope.row.container_state == 1
|
:type="scope.row.container_state == 0
|
||||||
? 'info'
|
? 'warning'
|
||||||
: scope.row.container_state == 2
|
: scope.row.container_state == 1
|
||||||
? 'danger'
|
? 'info'
|
||||||
: scope.row.container_state == 3
|
: scope.row.container_state == 2
|
||||||
? 'danger'
|
? 'success'
|
||||||
: 'success'"
|
: scope.row.container_state == 3
|
||||||
|
? 'info'
|
||||||
|
: scope.row.container_state == 4
|
||||||
|
? 'danger'
|
||||||
|
: 'info'"
|
||||||
effect="light"
|
effect="light"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
scope.row.container_state == 0
|
scope.row.container_state == 0
|
||||||
? "未构建"
|
? "未支付"
|
||||||
: scope.row.container_state == 1
|
: scope.row.container_state == 1
|
||||||
? "已构建"
|
? "未构建"
|
||||||
: scope.row.container_state == 2
|
: scope.row.container_state == 2
|
||||||
? "构建失败"
|
? "已构建"
|
||||||
: scope.row.container_state == 3
|
: scope.row.container_state == 3
|
||||||
? "已删除"
|
? "未知"
|
||||||
: "未知状态"
|
: scope.row.container_state == 4
|
||||||
|
? "已删除"
|
||||||
|
: "未知状态"
|
||||||
}}
|
}}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -251,7 +257,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
:icon="Setting"
|
:icon="Setting"
|
||||||
@click="$router.push('/servers/containers?container_id=' + scope.row.container_id)"
|
@click="$router.push('/servers/container?container_id=' + scope.row.container_id)"
|
||||||
>
|
>
|
||||||
管理
|
管理
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -610,7 +616,8 @@ import {
|
|||||||
getFloatingIpList,
|
getFloatingIpList,
|
||||||
addFloatingIp,
|
addFloatingIp,
|
||||||
delFloatingIp,
|
delFloatingIp,
|
||||||
addFloatingIpBatch
|
addFloatingIpBatch,
|
||||||
|
selectServerPlan
|
||||||
} from "@/utils/acs/server";
|
} from "@/utils/acs/server";
|
||||||
import { ElMessage, ElNotification } from 'element-plus';
|
import { ElMessage, ElNotification } from 'element-plus';
|
||||||
import { copyDomText } from "@/utils/hide";
|
import { copyDomText } from "@/utils/hide";
|
||||||
@@ -671,31 +678,43 @@ const initData = async () => {
|
|||||||
try {
|
try {
|
||||||
const userInfoRes = await getUserInfo();
|
const userInfoRes = await getUserInfo();
|
||||||
console.log("获取用户信息", userInfoRes);
|
console.log("获取用户信息", userInfoRes);
|
||||||
if (userInfoRes && userInfoRes.data && userInfoRes.data.data) {
|
if (userInfoRes && userInfoRes.data ) {
|
||||||
// 根据API返回的用户信息设置用户类型
|
console.log("获取用户信息", userInfoRes);
|
||||||
const userType = userInfoRes.data.is_admin == true ? "1" : "0";
|
localStorage.setItem("user_id", userInfoRes.data.user_id);
|
||||||
localStorage.setItem("user_id", userInfoRes.real_name.UserId);
|
|
||||||
localStorage.setItem("user_type", userType);
|
|
||||||
console.log("获取到用户类型", userType);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取用户信息失败:", error);
|
console.error("获取用户信息失败:", error);
|
||||||
// 如果获取失败,默认设置为管理员权限确保功能可用
|
|
||||||
localStorage.setItem("user_type", "1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有容器
|
// 获取所有容器
|
||||||
let usertype = localStorage.getItem("user_type");
|
|
||||||
console.log("用户类型", usertype);
|
|
||||||
if (usertype == "1") {
|
|
||||||
containerBox.server_id = route.query.server_id;
|
containerBox.server_id = route.query.server_id;
|
||||||
|
containerBox.user_id = localStorage.getItem("user_id");
|
||||||
let cons = await getContainer(containerBox);
|
let cons = await getContainer(containerBox);
|
||||||
console.log("获取容器列表", cons);
|
|
||||||
if (cons && cons.data) {
|
if (cons && cons.data) {
|
||||||
user_servers.value = cons.data.data || [];
|
user_servers.value = cons.data.data || [];
|
||||||
|
// user_servers.value = cons.data.data.forEach(async item => {
|
||||||
|
// item.plan_name = await selectServerPlan({
|
||||||
|
// server_type: 'dockerContainer',
|
||||||
|
// plan_id: item.plan_id
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
for (const item of user_servers.value){
|
||||||
|
try{
|
||||||
|
const res = await selectServerPlan({
|
||||||
|
server_type: 'dockerContainer',
|
||||||
|
plan_id: item.plan_id
|
||||||
|
})
|
||||||
|
console.log("获取容器规格:", res);
|
||||||
|
item.plan_name = res.data.data.name;
|
||||||
|
}catch(error){
|
||||||
|
console.error("获取容器列表失败:", error);
|
||||||
|
ElMessage.error("获取容器列表失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("获取容器列表", user_servers.value);
|
||||||
total.value = cons.data.count || 0;
|
total.value = cons.data.count || 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("初始化数据失败:", error);
|
console.error("初始化数据失败:", error);
|
||||||
ElMessage.error("加载数据失败,请刷新页面重试");
|
ElMessage.error("加载数据失败,请刷新页面重试");
|
||||||
@@ -727,7 +746,6 @@ const GetSpecs = async () => {
|
|||||||
server_id: route.query.server_id,
|
server_id: route.query.server_id,
|
||||||
count: 30
|
count: 30
|
||||||
});
|
});
|
||||||
console.log("服务器实例规格", plans.data.data);
|
|
||||||
spec_list.value = plans.data.data;
|
spec_list.value = plans.data.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取实例规格列表失败:", error);
|
console.error("获取实例规格列表失败:", error);
|
||||||
@@ -929,8 +947,9 @@ const editSpec = async () => {
|
|||||||
server_id: route.query.server_id,
|
server_id: route.query.server_id,
|
||||||
server_type:TypeData.value
|
server_type:TypeData.value
|
||||||
});
|
});
|
||||||
|
console.log("添加实例规格:",res)
|
||||||
|
|
||||||
if (res.code == 200) {
|
if (res.data.code == 200) {
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: '添加成功',
|
title: '添加成功',
|
||||||
message: `已成功添加规格 "${spec_form.name}"`,
|
message: `已成功添加规格 "${spec_form.name}"`,
|
||||||
@@ -939,7 +958,7 @@ const editSpec = async () => {
|
|||||||
});
|
});
|
||||||
centerDialogVisible.value = false;
|
centerDialogVisible.value = false;
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.data.message || "添加失败");
|
ElMessage.error(res.data.msg || "添加失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
@@ -8,5 +9,17 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'@': resolve(__dirname, 'src')
|
'@': resolve(__dirname, 'src')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['monaco-editor']
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'monaco-editor': ['monaco-editor']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user