diff --git a/web/package-lock.json b/web/package-lock.json
index a20ff22..3e17d32 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -16,6 +16,7 @@
"@vueuse/core": "^14.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
+ "ansi-to-html": "^0.7.2",
"apexcharts": "^5.3.6",
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
@@ -799,6 +800,30 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/ansi-to-html": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz",
+ "integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^2.2.0"
+ },
+ "bin": {
+ "ansi-to-html": "bin/ansi-to-html"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/ansi-to-html/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
diff --git a/web/package.json b/web/package.json
index d154b9d..9ccc756 100644
--- a/web/package.json
+++ b/web/package.json
@@ -17,6 +17,7 @@
"@vueuse/core": "^14.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
+ "ansi-to-html": "^0.7.2",
"apexcharts": "^5.3.6",
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
@@ -54,4 +55,4 @@
"vite": "$vite"
}
}
-}
\ No newline at end of file
+}
diff --git a/web/src/utils/ansi.ts b/web/src/utils/ansi.ts
new file mode 100644
index 0000000..e0776fc
--- /dev/null
+++ b/web/src/utils/ansi.ts
@@ -0,0 +1,21 @@
+import AnsiUp from 'ansi-to-html'
+
+const ansiUp = new AnsiUp({
+ newline: false,
+ escapeXML: true,
+ stream: false
+})
+
+export function ansiToHtml(ansi: string): string {
+ if (!ansi) return ''
+ return ansiUp.toHtml(ansi)
+}
+
+export function highlightHtml(html: string, keyword: string): string {
+ if (!keyword.trim()) return html
+
+ const escaped = keyword.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ // Match keyword but not inside HTML tags
+ const regex = new RegExp(`(${escaped})(?![^<]*>)`, 'gi')
+ return html.replace(regex, '$1')
+}
diff --git a/web/src/views/dependencies/Dependencies.vue b/web/src/views/dependencies/Dependencies.vue
index fde5065..b1bb470 100644
--- a/web/src/views/dependencies/Dependencies.vue
+++ b/web/src/views/dependencies/Dependencies.vue
@@ -133,6 +133,12 @@ async function uninstallPackage() {
}
}
+import { ansiToHtml } from '@/utils/ansi'
+
+const renderedLog = computed(() => {
+ return ansiToHtml(logContent.value)
+})
+
function showLog(dep: Dependency) {
logPkgName.value = dep.name
logContent.value = dep.log || '暂无日志'
@@ -357,7 +363,7 @@ onMounted(async () => {
{{ logContent }}
+
{{ decompressedOutput }}
+ class="p-4 text-xs font-mono whitespace-pre-wrap break-all log-pre leading-relaxed" v-html="renderedOutput">