Colors in logs
This commit is contained in:
@@ -10,6 +10,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Log } from "@/lib/types";
|
||||
import { getAllContainerLogs } from "@/lib/dockship/docker";
|
||||
import { useAsyncInterval } from "@/hooks/use-async";
|
||||
import { AnsiText } from "@/components/ansi/Ansi";
|
||||
|
||||
export default function LogsPage() {
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
@@ -182,9 +183,10 @@ export default function LogsPage() {
|
||||
</span>
|
||||
|
||||
{/* Message */}
|
||||
<span className="text-foreground wrap-break-words">
|
||||
{/* <span className="text-foreground wrap-break-words">
|
||||
{log.message}
|
||||
</span>
|
||||
</span> */}
|
||||
<AnsiText text={log.message} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { tokenizeAnsi } from "./tokenize";
|
||||
|
||||
type AnsiTextProps = {
|
||||
text: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const ANSI_TW_CLASSES: Record<string, string> = {
|
||||
black: "text-black",
|
||||
red: "text-red-500",
|
||||
green: "text-emerald-500",
|
||||
yellow: "text-yellow-400",
|
||||
blue: "text-blue-400",
|
||||
magenta: "text-purple-400",
|
||||
cyan: "text-cyan-400",
|
||||
white: "text-gray-100",
|
||||
|
||||
brightBlack: "text-gray-500",
|
||||
brightRed: "text-red-400",
|
||||
brightGreen: "text-emerald-400",
|
||||
brightYellow: "text-yellow-300",
|
||||
brightBlue: "text-blue-300",
|
||||
brightMagenta: "text-purple-300",
|
||||
brightCyan: "text-cyan-300",
|
||||
brightWhite: "text-white",
|
||||
};
|
||||
|
||||
export function AnsiText({ text, className }: AnsiTextProps) {
|
||||
const tokens = tokenizeAnsi(text);
|
||||
|
||||
return (
|
||||
<span className={`whitespace-pre-wrap font-mono ${className ?? ""}`}>
|
||||
{tokens.map((token, i) => {
|
||||
const classes = [
|
||||
token.style.color && ANSI_TW_CLASSES[token.style.color],
|
||||
token.style.bold && "font-semibold",
|
||||
token.style.underline && "underline",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
return (
|
||||
<span key={i} className={classes}>
|
||||
{token.text}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
export type StyleState = {
|
||||
color?: string;
|
||||
bold?: boolean;
|
||||
underline?: boolean;
|
||||
};
|
||||
|
||||
export const ANSI_REGEX = /\u001b\[([0-9;]*)m/g;
|
||||
|
||||
const COLOR_CODES: Record<number, string> = {
|
||||
30: "black",
|
||||
31: "red",
|
||||
32: "green",
|
||||
33: "yellow",
|
||||
34: "blue",
|
||||
35: "magenta",
|
||||
36: "cyan",
|
||||
37: "white",
|
||||
|
||||
90: "brightBlack",
|
||||
91: "brightRed",
|
||||
92: "brightGreen",
|
||||
93: "brightYellow",
|
||||
94: "brightBlue",
|
||||
95: "brightMagenta",
|
||||
96: "brightCyan",
|
||||
97: "brightWhite",
|
||||
};
|
||||
|
||||
export function applyAnsiCodes(codes: number[], state: StyleState): StyleState {
|
||||
let next = { ...state };
|
||||
|
||||
for (const code of codes) {
|
||||
if (code === 0) {
|
||||
next = {};
|
||||
} else if (code === 1) {
|
||||
next.bold = true;
|
||||
} else if (code === 4) {
|
||||
next.underline = true;
|
||||
} else if (COLOR_CODES[code]) {
|
||||
next.color = COLOR_CODES[code];
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ANSI_REGEX, applyAnsiCodes, StyleState } from "./parser";
|
||||
|
||||
export type Token = {
|
||||
text: string;
|
||||
style: StyleState;
|
||||
};
|
||||
|
||||
export function tokenizeAnsi(input: string): Token[] {
|
||||
const tokens: Token[] = [];
|
||||
|
||||
let lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
let style: StyleState = {};
|
||||
|
||||
while ((match = ANSI_REGEX.exec(input))) {
|
||||
const index = match.index;
|
||||
|
||||
if (index > lastIndex) {
|
||||
tokens.push({
|
||||
text: input.slice(lastIndex, index),
|
||||
style,
|
||||
});
|
||||
}
|
||||
|
||||
const codes = match[1].split(";").map(Number);
|
||||
style = applyAnsiCodes(codes, style);
|
||||
|
||||
lastIndex = ANSI_REGEX.lastIndex;
|
||||
}
|
||||
|
||||
if (lastIndex < input.length) {
|
||||
tokens.push({
|
||||
text: input.slice(lastIndex),
|
||||
style,
|
||||
});
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
Reference in New Issue
Block a user