Skip to content

Commit

Permalink
Merge pull request #912 from drudge/new-ansi-logs
Browse files Browse the repository at this point in the history
feat(logs): support ansi codes
  • Loading branch information
Siumauricio authored Dec 18, 2024
2 parents 20d5913 + 6bf85bc commit 852895c
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 12 deletions.
44 changes: 32 additions & 12 deletions apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { cn } from "@/lib/utils";
import { escapeRegExp } from "lodash";
import React from "react";
import { type LogLine, getLogType } from "./utils";
import { type LogLine, getLogType, parseAnsi } from "./utils";

interface LogLineProps {
log: LogLine;
Expand All @@ -33,18 +33,38 @@ export function TerminalLine({ log, noTimestamp, searchTerm }: LogLineProps) {
: "--- No time found ---";

const highlightMessage = (text: string, term: string) => {
if (!term) return text;
if (!term) {
const segments = parseAnsi(text);
return segments.map((segment, index) => (
<span key={index} className={segment.className || undefined}>
{segment.text}
</span>
));
}

const parts = text.split(new RegExp(`(${escapeRegExp(term)})`, "gi"));
return parts.map((part, index) =>
part.toLowerCase() === term.toLowerCase() ? (
<span key={index} className="bg-yellow-200 dark:bg-yellow-900">
{part}
// For search, we need to handle both ANSI and search highlighting
const segments = parseAnsi(text);
return segments.map((segment, index) => {
const parts = segment.text.split(
new RegExp(`(${escapeRegExp(term)})`, "gi"),
);
return (
<span key={index} className={segment.className || undefined}>
{parts.map((part, partIndex) =>
part.toLowerCase() === term.toLowerCase() ? (
<span
key={partIndex}
className="bg-yellow-200 dark:bg-yellow-900"
>
{part}
</span>
) : (
part
),
)}
</span>
) : (
part
),
);
);
});
};

const tooltip = (color: string, timestamp: string | null) => {
Expand Down Expand Up @@ -104,7 +124,7 @@ export function TerminalLine({ log, noTimestamp, searchTerm }: LogLineProps) {
</Badge>
</div>
<span className="dark:text-gray-200 font-mono text-foreground whitespace-pre-wrap break-all">
{searchTerm ? highlightMessage(message, searchTerm) : message}
{highlightMessage(message, searchTerm || "")}
</span>
</div>
);
Expand Down
94 changes: 94 additions & 0 deletions apps/dokploy/components/dashboard/docker/logs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,47 @@ interface LogStyle {
variant: LogVariant;
color: string;
}
interface AnsiSegment {
text: string;
className: string;
}

const ansiToTailwind: Record<number, string> = {
// Reset
0: "",
// Regular colors
30: "text-black dark:text-gray-900",
31: "text-red-600 dark:text-red-500",
32: "text-green-600 dark:text-green-500",
33: "text-yellow-600 dark:text-yellow-500",
34: "text-blue-600 dark:text-blue-500",
35: "text-purple-600 dark:text-purple-500",
36: "text-cyan-600 dark:text-cyan-500",
37: "text-gray-600 dark:text-gray-400",
// Bright colors
90: "text-gray-500 dark:text-gray-600",
91: "text-red-500 dark:text-red-600",
92: "text-green-500 dark:text-green-600",
93: "text-yellow-500 dark:text-yellow-600",
94: "text-blue-500 dark:text-blue-600",
95: "text-purple-500 dark:text-purple-600",
96: "text-cyan-500 dark:text-cyan-600",
97: "text-white dark:text-gray-300",
// Background colors
40: "bg-black",
41: "bg-red-600",
42: "bg-green-600",
43: "bg-yellow-600",
44: "bg-blue-600",
45: "bg-purple-600",
46: "bg-cyan-600",
47: "bg-white",
// Formatting
1: "font-bold",
2: "opacity-75",
3: "italic",
4: "underline",
};

const LOG_STYLES: Record<LogType, LogStyle> = {
error: {
Expand Down Expand Up @@ -150,3 +191,56 @@ export const getLogType = (message: string): LogStyle => {

return LOG_STYLES.info;
};

export function parseAnsi(text: string) {
const segments: { text: string; className: string }[] = [];
let currentIndex = 0;
let currentClasses: string[] = [];

while (currentIndex < text.length) {
const escStart = text.indexOf("\x1b[", currentIndex);

// No more escape sequences found
if (escStart === -1) {
if (currentIndex < text.length) {
segments.push({
text: text.slice(currentIndex),
className: currentClasses.join(" "),
});
}
break;
}

// Add text before escape sequence
if (escStart > currentIndex) {
segments.push({
text: text.slice(currentIndex, escStart),
className: currentClasses.join(" "),
});
}

const escEnd = text.indexOf("m", escStart);
if (escEnd === -1) break;

// Handle multiple codes in one sequence (e.g., \x1b[1;31m)
const codesStr = text.slice(escStart + 2, escEnd);
const codes = codesStr.split(";").map((c) => Number.parseInt(c, 10));

if (codes.includes(0)) {
// Reset all formatting
currentClasses = [];
} else {
// Add new classes for each code
for (const code of codes) {
const className = ansiToTailwind[code];
if (className && !currentClasses.includes(className)) {
currentClasses.push(className);
}
}
}

currentIndex = escEnd + 1;
}

return segments;
}

0 comments on commit 852895c

Please sign in to comment.