Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature / Improve logs view #189

Merged
merged 6 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions client/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<v-app>

<v-app-bar dense max-height="50" :color="banner.bgcolor" v-if="banner.show">
<v-app-bar dense max-height="50" :color="banner.bgcolor" v-if="banner.show && popup!='true'">
<v-toolbar-title style="width: 100%; text-align: center; color: azure;">{{ banner.message }}</v-toolbar-title>
</v-app-bar>
<v-navigation-drawer
Expand All @@ -10,7 +10,7 @@
expand-on-hover
permanent
mini-variant
v-if="isAuthenticated"
v-if="isAuthenticated && popup!='true'"
>
<v-list nav dense>
<v-list-item link to="/">
Expand Down Expand Up @@ -131,6 +131,9 @@ export default {
},
*/
created() {
if (this.$route.query.popup) {
this.popup = this.$route.query.popup;
}
this.$vuetify.theme.dark = this.getTheme();
this.checkSession()
},
Expand All @@ -141,6 +144,7 @@ export default {
this.checkSession();
},
data: () => ({
popup: "false",
session: false,
isAuthenticated: false,
version: "dev",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/apps/detail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default {
},
components: {
events : () => import('./events.vue'),
logs : () => import('./logs.vue'),
logs : () => import('./logstab.vue'),
vulnerabilities : () => import('./vulnerabilities.vue'),
breadcrumbs: () => import('../breadcrumbs.vue'),
},
Expand Down
34 changes: 12 additions & 22 deletions client/src/components/apps/logs.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
<template>
<v-container>
<v-row class="justify-space-between">
<v-col cols="6" sm="6" md="6" lg="6" xl="6">
<h1>Logs for {{ this.app }}</h1>
<p></p>
</v-col>
</v-row>
<v-row style="height: 1100px">
<v-col cols="12" sm="12" md="12">
<div class="console" id="console">
<div v-for="line in loglines" :key="line.id">
{{ new Date(line.time).toISOString()}}<span :style="'color:' +line.color">[{{ line.podID }}/{{ line.container.replace('kuberoapp-', '') }}]</span>
{{ line.log }}
</div>
</div>
</v-col>
</v-row>

</v-container>
<div class="console" id="console" style="height: 100%;">
<div v-for="line in loglines" :key="line.id">
{{ new Date(line.time).toISOString()}}<span :style="'color:' +line.color">[{{ line.podID }}/{{ line.container.replace('kuberoapp-', '') }}]</span>
{{ line.log }}
</div>
</div>
</template>

<script>
Expand All @@ -29,6 +16,7 @@ export default {
},
},
mounted() {
this.getLogHistory()
this.socketJoin()
this.startLogs()
},
Expand Down Expand Up @@ -66,8 +54,6 @@ export default {
*/
],
}),
components: {
},
methods: {
socketJoin() {
this.$socket.client.emit("join", {
Expand All @@ -84,6 +70,11 @@ export default {
console.log("logs started");
});
},
getLogHistory() {
axios.get(`/api/logs/${this.pipeline}/${this.phase}/${this.app}/history`).then((response) => {
this.loglines = response.data;
});
},
},
}
</script>
Expand All @@ -96,7 +87,6 @@ a:link { text-decoration: none;}
}
.console {
height: 100%;
max-height: 1000px;
overflow-x: scroll;
background-color: #333;
color: #c0c0c0;
Expand Down
68 changes: 68 additions & 0 deletions client/src/components/apps/logstab.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<v-container>
<v-row class="justify-space-between">
<v-col cols="6" sm="6" md="6" lg="6" xl="6">
<h1>
Logs for {{ this.app }}

<v-btn
class="ma-2"
@click="openInWindow"
>
Open Logs
<v-icon
right
dark
>
mdi-open-in-new
</v-icon>
</v-btn>
</h1>
<p></p>
</v-col>
</v-row>
<v-row style="height: 1100px">
<v-col cols="12" sm="12" md="12">
<logs :pipeline=pipeline :phase=phase :app=app></logs>
</v-col>
</v-row>

</v-container>
</template>

<script>
export default {
props: {
pipeline: {
type: String,
default: "MISSING"
},
phase: {
type: String,
default: "MISSING"
},
app: {
type: String,
default: "new"
}
},
data: () => ({
}),
components: {
logs: () => import('./logs.vue'),
},
methods: {
openInWindow() {
window.open(`#/pipeline/${this.pipeline}/${this.phase}/${this.app}/logspopup?popup=true`, '_blank', 'popup=yes,location=no,height=1000,width=1000,scrollbars=yes,status=no');
}
},
}
</script>

<style lang="scss">

a:link { text-decoration: none;}
.v-icon.v-icon {
vertical-align:inherit;
}
</style>
7 changes: 7 additions & 0 deletions client/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PipelineNew from "@/components/pipelines/new"
import PipelineDetails from "@/components/pipelines/detail"
import AppsNew from "@/components/apps/new"
import AppsDetail from "@/components/apps/detail"
import AppsLogsPopUp from "@/components/apps/logs"
import Settingsform from "@/components/settings/form"
import Addonslist from "@/components/addons/list"
import EventsView from "@/components/events/view"
Expand Down Expand Up @@ -52,6 +53,12 @@ export default new VueRouter({
component: AppsDetail,
props: true
},
{
path: "/pipeline/:pipeline/:phase/:app/logspopup",
name: "App logs popup",
component: AppsLogsPopUp,
props: true
},
{
path: "/settings",
name: "Settigns",
Expand Down
59 changes: 58 additions & 1 deletion src/kubero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ export class Kubero {

if (!this.podLogStreams.includes(podName)) {

this.kubectl.log.log(namespace, podName, container, logStream, {follow: true, tailLines: 50, pretty: false, timestamps: false})
this.kubectl.log.log(namespace, podName, container, logStream, {follow: true, tailLines: 0, pretty: false, timestamps: false})
.then(res => {
debug.log('logs started for '+podName+' '+container);
this.podLogStreams.push(podName);
Expand Down Expand Up @@ -737,6 +737,63 @@ export class Kubero {
}
}

public async getLogsHistory(pipelineName: string, phaseName: string, appName: string) {
const contextName = this.getContext(pipelineName, phaseName);
const namespace = pipelineName+'-'+phaseName;

const logStream = new Stream.PassThrough();
let logs: String = '';
logStream.on('data', (chunk: any) => {
//console.log(chunk.toString());
logs += chunk.toString();
});

let loglines: any[] = [];
if (contextName) {
const pods = await this.kubectl.getPods(namespace, contextName);
for (const pod of pods) {

if (pod.metadata?.name?.startsWith(appName)) {
for (const container of pod.spec?.containers || []) {
console.log('getting logs for '+pod.metadata.name+' '+container.name);
await this.kubectl.log.log(namespace, pod.metadata.name, container.name, logStream, {follow: false, tailLines: 80, pretty: false, timestamps: true})

// sleep for 1 second to wait for all logs to be collected
await new Promise(r => setTimeout(r, 1000));

// split loglines into array
const loglinesArray = logs.split('\n').reverse();
for (const logline of loglinesArray) {
if (logline.length > 0) {
// split after first whitespace
const loglineArray = logline.split(/(?<=^\S+)\s/);
const loglineDate = new Date(loglineArray[0]);
const loglineText = loglineArray[1];



loglines.push({
id: uuidv4(),
time: loglineDate.getTime(),
pipeline: pipelineName,
phase: phaseName,
app: appName,
pod: pod.metadata.name,
podID: pod.metadata.name.split('-')[3]+'-'+pod.metadata.name.split('-')[4],
container: container.name,
color: this.logcolor(pod.metadata.name),
log: loglineText
});
}
}
}
}
}
}

return loglines;
}

public getRepositories() {
let repositories = {
github: false,
Expand Down
16 changes: 16 additions & 0 deletions src/routes/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@
res.send('ok');
});

Router.get('/logs/:pipeline/:phase/:app/history', authMiddleware, async function (req: Request, res: Response) {
Dismissed Show dismissed Hide dismissed
// #swagger.tags = ['UI']
// #swagger.summary = 'Get logs history for a specific app'
// #swagger.description = 'Get logs history for a specific app'
// #swagger.parameters['pipeline'] = { description: 'Pipeline name' }
// #swagger.parameters['phase'] = { description: 'Phase name' }
// #swagger.parameters['app'] = { description: 'App name' }

const logs = await req.app.locals.kubero.getLogsHistory(
req.params.pipeline,
req.params.phase,
req.params.app
);
res.send(logs);
});

Router.get('/events', authMiddleware, async function (req: Request, res: Response) {
// #swagger.tags = ['UI']
// #swagger.summary = 'Get the Kubero Kubernetes events'
Expand Down
2 changes: 1 addition & 1 deletion swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const swaggerAutogen = require('swagger-autogen')({openapi: '3.0.0'})
// https://github.com/davibaltar/swagger-autogen
const doc = {
info: {
version: '1.8.0',
version: '1.10.1',
title: 'Kubero',
description: 'Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.',
},
Expand Down
Loading