mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-20 15:36:30 +00:00
Fix log view (#1874)
closes #1857 closes #1520 closes #1879 - fixes unicode log lines as reported in matrix --------- Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
3714e9c218
commit
3d435a9cb7
3 changed files with 56 additions and 15 deletions
|
@ -186,8 +186,8 @@ func LogStreamSSE(c *gin.Context) {
|
|||
}
|
||||
|
||||
if step.State != model.StatusRunning {
|
||||
log.Debug().Msg("stream not found.")
|
||||
logWriteStringErr(io.WriteString(rw, "event: error\ndata: stream not found\n\n"))
|
||||
log.Debug().Msg("step not running (anymore).")
|
||||
logWriteStringErr(io.WriteString(rw, "event: error\ndata: step not running (anymore)\n\n"))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -61,12 +61,12 @@ func (l *log) Open(_ context.Context, stepID int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (l *log) Write(_ context.Context, stepID int64, logEntry *model.LogEntry) error {
|
||||
func (l *log) Write(ctx context.Context, stepID int64, logEntry *model.LogEntry) error {
|
||||
l.Lock()
|
||||
s, ok := l.streams[stepID]
|
||||
l.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
return l.Open(ctx, stepID)
|
||||
}
|
||||
s.Lock()
|
||||
s.list = append(s.list, logEntry)
|
||||
|
|
|
@ -34,13 +34,41 @@
|
|||
<div
|
||||
v-show="hasLogs && loadedLogs"
|
||||
ref="consoleElement"
|
||||
class="w-full max-w-full grid grid-cols-[min-content,1fr,min-content] auto-rows-min flex-grow p-2 gap-x-2 overflow-x-hidden overflow-y-auto"
|
||||
class="w-full max-w-full grid grid-cols-[min-content,1fr,min-content] auto-rows-min flex-grow p-2 overflow-x-hidden overflow-y-auto"
|
||||
>
|
||||
<div v-for="line in log" :id="`L${line.index}`" :key="line.index" class="contents font-mono">
|
||||
<span class="text-gray-500 whitespace-nowrap select-none text-right">{{ line.index + 1 }}</span>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span class="align-top text-color whitespace-pre-wrap break-words" v-html="line.text" />
|
||||
<span class="text-gray-500 whitespace-nowrap select-none text-right">{{ formatTime(line.time) }}</span>
|
||||
<div v-for="line in log" :key="line.index" class="contents font-mono">
|
||||
<a
|
||||
:id="`L${line.index}`"
|
||||
:href="`#L${line.index}`"
|
||||
class="text-gray-500 whitespace-nowrap select-none text-right pl-1 pr-2"
|
||||
:class="{
|
||||
'bg-opacity-40 dark:bg-opacity-50 bg-red-600 dark:bg-red-800': line.type === 'error',
|
||||
'bg-opacity-40 dark:bg-opacity-50 bg-yellow-600 dark:bg-yellow-800': line.type === 'warning',
|
||||
'bg-opacity-20 bg-blue-600': $route.hash === `#L${line.index}`,
|
||||
underline: $route.hash === `#L${line.index}`,
|
||||
}"
|
||||
>{{ line.index + 1 }}</a
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span
|
||||
class="align-top text-color whitespace-pre-wrap break-words"
|
||||
:class="{
|
||||
'bg-opacity-40 dark:bg-opacity-50 bg-red-600 dark:bg-red-800': line.type === 'error',
|
||||
'bg-opacity-40 dark:bg-opacity-50 bg-yellow-600 dark:bg-yellow-800': line.type === 'warning',
|
||||
'bg-opacity-20 bg-blue-600': $route.hash === `#L${line.index}`,
|
||||
}"
|
||||
v-html="line.text"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<span
|
||||
class="text-gray-500 whitespace-nowrap select-none text-right pr-1"
|
||||
:class="{
|
||||
'bg-opacity-40 dark:bg-opacity-50 bg-red-600 dark:bg-red-800': line.type === 'error',
|
||||
'bg-opacity-40 dark:bg-opacity-50 bg-yellow-600 dark:bg-yellow-800': line.type === 'warning',
|
||||
'bg-opacity-20 bg-blue-600': $route.hash === `#L${line.index}`,
|
||||
}"
|
||||
>{{ formatTime(line.time) }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -84,6 +112,7 @@ type LogLine = {
|
|||
index: number;
|
||||
text: string;
|
||||
time?: number;
|
||||
type: 'error' | 'warning' | null;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -128,14 +157,26 @@ function formatTime(time?: number): string {
|
|||
return time === undefined ? '' : `${time}s`;
|
||||
}
|
||||
|
||||
function writeLog(line: LogLine) {
|
||||
function writeLog(line: Partial<LogLine>) {
|
||||
logBuffer.value.push({
|
||||
index: line.index ?? 0,
|
||||
text: ansiUp.value.ansi_to_html(line.text),
|
||||
text: ansiUp.value.ansi_to_html(line.text ?? ''),
|
||||
time: line.time ?? 0,
|
||||
type: null, // TODO: implement way to detect errors and warnings
|
||||
});
|
||||
}
|
||||
|
||||
// SOURCE: https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
|
||||
function b64DecodeUnicode(str: string) {
|
||||
return decodeURIComponent(
|
||||
window
|
||||
.atob(str)
|
||||
.split('')
|
||||
.map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
|
||||
.join(''),
|
||||
);
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
nextTick(() => {
|
||||
if (!consoleElement.value) {
|
||||
|
@ -198,7 +239,7 @@ async function download() {
|
|||
downloadInProgress.value = false;
|
||||
}
|
||||
const fileURL = window.URL.createObjectURL(
|
||||
new Blob([logs.map((line) => atob(line.data)).join('')], {
|
||||
new Blob([logs.map((line) => b64DecodeUnicode(line.data)).join('')], {
|
||||
type: 'text/plain',
|
||||
}),
|
||||
);
|
||||
|
@ -240,13 +281,13 @@ async function loadLogs() {
|
|||
|
||||
if (isStepFinished(step.value)) {
|
||||
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
|
||||
logs?.forEach((line) => writeLog({ index: line.line, text: atob(line.data), time: line.time }));
|
||||
logs?.forEach((line) => writeLog({ index: line.line, text: b64DecodeUnicode(line.data), time: line.time }));
|
||||
flushLogs(false);
|
||||
}
|
||||
|
||||
if (isStepRunning(step.value)) {
|
||||
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
|
||||
writeLog({ index: line.line, text: atob(line.data), time: line.time });
|
||||
writeLog({ index: line.line, text: b64DecodeUnicode(line.data), time: line.time });
|
||||
flushLogs(true);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue