Skip to content

Commit

Permalink
Finalize Tekton UI
Browse files Browse the repository at this point in the history
  • Loading branch information
sebt3 committed May 5, 2024
1 parent ce5e892 commit e3aac6b
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 42 deletions.
2 changes: 1 addition & 1 deletion front/components/k8s/ReplicaSetTabLogs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const logViewSplitterModel= ref(20)
</template>
<template v-slot:after>
<q-tab-panels v-model="logViewTab" animated swipeable vertical transition-prev="jump-up" transition-next="jump-up">
<q-tab-panel v-for="prop in props.model.childk8sPod.filter(x=>x!=undefined).map(pod=>pod.childcoreContainer.filter(c=>!c.init||['Job','ReplicaSet','DaemonSet','StatefulSet'].includes(props.short))).flat()" v-bind:key="prop.name" :name="prop.name" class="bg-black text-white">
<q-tab-panel v-for="prop in props.model.childk8sPod.filter(x=>x!=undefined).map(pod=>pod.childcoreContainer).flat()" v-bind:key="prop.name" :name="prop.name" class="bg-black text-white">
<pre v-if="prop.getcoreLog != undefined && Array.isArray(prop.getcoreLog.lines)">
{{ prop.getcoreLog.lines.join('\n') }}
</pre>
Expand Down
121 changes: 121 additions & 0 deletions front/components/tekton/PipelineMeta.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
model: object
}>();
const stepWidth = 100
const stepHeight = 20
const stepVMargin = 5
const stages = computed(() => {
if (props.model.spec == undefined || !Array.isArray(props.model.spec.tasks)) return []
if (props.model.spec.tasks.filter(t=>Array.isArray(t.runAfter)).length<1) return [props.model.spec.tasks]
const reducer = (acc,name)=>acc && stages.flat().map(s=>s.name).includes(name)
const stages = [props.model.spec.tasks.filter(t=>!Array.isArray(t.runAfter))]
var to_assign = props.model.spec.tasks.filter(t=>Array.isArray(t.runAfter))
if (stages[0].length<1) {
console.error('No starting point')
return []
}
for (var cont = true;cont;) {
const founds = to_assign.filter(t=>t.runAfter.reduce(reducer,true))
if(founds.length>0) {
to_assign = to_assign.filter(t=>!t.runAfter.reduce(reducer,true))
stages.push(founds)
cont = to_assign.length>0
} else {
cont=false;
console.error('no more step to stages, is this a DAG?')
}
}
if (Array.isArray(props.model.spec.finally) && props.model.spec.finally.length>0) {
stages.push(props.model.spec.finally)
}
return stages
})
const virtLinks = computed(() => {
const stgs = stages.value;
const getX = name => stgs.reduce((acc,cur,i)=>cur.map(x=>x.name).includes(name)?i:acc,0)
const getY = name => stgs.reduce((acc,cur)=>cur.map(x=>x.name).includes(name)?cur.map(x=>x.name).indexOf(name):acc,0)
const links = stgs.flat().filter(stg=>Array.isArray(stg.runAfter)&&stg.runAfter.length>0).map(dest=>dest.runAfter.map(src=>{return {
src: src,
dst: dest.name,
six: getX(src),
siy: getY(src),
dix: getX(dest.name),
diy: getY(dest.name),
}})).flat()
const columns = Object.entries(Object.groupBy(links,({six})=>six)).map(([_,lnks],col)=>{
const groups= Object.entries(Object.groupBy(lnks,({src})=>src)).filter(([_,a])=>a.length>1).concat(Object.entries(Object.groupBy(lnks,({dst})=>dst)).filter(([_,a])=>a.length>1)).map(([_,a])=>a)
groups.push(...lnks.filter(l=>!groups.flat().map(i=>`${i.src}-${i.dst}`).includes(`${l.src}-${l.dst}`)).map(l=>[l]))
return groups.map((g,i)=>g.map(l=>{return {...l,col:col, mi:i,mx:groups.length}}))
}).flat().flat()
return columns
})
const width = computed(() => stages.value.length*(stepWidth+stepHMargin.value))
const height = computed(() => (stages.value.reduce((acc,cur)=>acc>cur.length?acc:cur.length,1)+1)*(stepHeight+stepVMargin)+stepVMargin)
const stepHMargin = computed(() => 10 + 30 * virtLinks.value.reduce((acc,cur)=>acc>cur.mx?acc:cur.mx,0))
const getProjectedX = x => x*(stepWidth+stepHMargin.value)+5
const getProjectedY = (y, max) => (y+1)*(height.value/(max+1))
const getPath = link => `
M${link.startX} ${link.startY}
L${link.midX-10} ${link.startY}
Q${link.midX} ${link.startY} ${link.midX} ${(link.midY<link.startY?Math.max:Math.min)(link.startY+(link.midY<link.startY?-10:link.midY==link.startY?0:10),link.midY)}
L${link.midX} ${(link.midY<link.destY?Math.max:Math.min)(link.destY+(link.midY<link.destY?-10:link.midY==link.destY?0:10),link.midY)}
Q${link.midX} ${link.destY} ${link.destX-10} ${link.destY}
L${link.destX-5} ${link.destY}
L${link.destX-5} ${link.destY-3}
L${link.destX-1} ${link.destY}
L${link.destX-5} ${link.destY+3}
L${link.destX-5} ${link.destY}`
const getCondition = name => {
if (props.model.spec == undefined || !Array.isArray(props.model.spec.tasks)) return null;
const myTasks = (props.model.spec.tasks.concat(Array.isArray(props.model.spec.finally)?props.model.spec.finally:[])).filter(t=>t.name==name)
if (myTasks.length>0 && Array.isArray(myTasks[0].when))
return myTasks[0].when
return null
}
const haveCondition = task => getCondition(task)!=null
const links = computed(() => {
const stgs = stages.value;
const hMargin = stepHMargin.value;
return virtLinks.value.map(link=>{return{...link,
c1: 5,
c2: 5,
destX: getProjectedX(link.dix)-(haveCondition(link.dst)?4:0),
startX: getProjectedX(link.six)+stepWidth,
destY: getProjectedY(link.diy,stgs[link.col+1].length)+stepHeight/2,
startY: getProjectedY(link.siy,stgs[link.col].length)+stepHeight/2,
midX: getProjectedX(link.six)+stepWidth+(hMargin/2)+((hMargin/4)/link.mx*link.mi*(link.mi%2==0?1:-1)),
midY: (getProjectedY(link.diy,stgs[link.col+1].length)+getProjectedY(link.siy,stgs[link.col].length)+stepHeight)/2,
}}).map(link=>{return{...link,d:getPath(link)}})
})
</script>
<template><div>
<svg ref="svgRoot" :viewBox="[0,0,width,height]" :width="width" :height="height" stroke-linejoin="round" stroke-linecap="round" style="width: 100%; height: auto; font: 10px sans-serif;">
<g class="links" v-for="link in links" :key="`${link.src}-${link.dst}`">
<path :d="link.d" stroke="black" stroke-width="1" fill="none" />
</g>
<g class="tasks" v-for="(steps, x) in stages" :key="`rects-${x}`">
<rect v-for="(task, y) in steps" :key="`rect-${task.name}`"
:width="stepWidth" :height="stepHeight" rx="5" ry="5"
:x="getProjectedX(x)" :y="getProjectedY(y, steps.length)"
class="isSkipped" />
</g>
<g class="labels" text-anchor="middle" v-for="(steps, x) in stages" :key="`labels-${x}`">
<text v-for="(task, y) in steps" :key="`text-${task.name}`"
:x="getProjectedX(x)+stepWidth/2" :y="getProjectedY(y, steps.length)+stepHeight-8"
class="isSkipped"
>{{ task['name'] }}</text>
</g>
<g class="conditions" v-for="(steps, x) in stages" :key="`conds-${x}`">
<g v-for="(task, y) in steps" :key="`cond-${task.name}`" :transform="`translate(${getProjectedX(x)-3} ${getProjectedY(y, steps.length)+stepHeight/2-3})`">
<rect v-if="haveCondition(task.name)" :transform="`rotate(45 3 3)`" width="6" height="6" class="isSkipped" />
</g>
</g>
</svg>
</div></template>
<style scoped lang="sass">
@use "quasar/src/css/variables" as q
text.isSkipped
fill: q.$grey
</style>
88 changes: 60 additions & 28 deletions front/components/tekton/PipelineRunMeta.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,29 @@ const getProjectedY = (y, max) => (y+1)*(height.value/(max+1))
const getPath = link => `
M${link.startX} ${link.startY}
L${link.midX-10} ${link.startY}
Q${link.midX} ${link.startY} ${link.midX} ${link.startY+10*(link.midY<link.startY?-1:link.midY==link.startY?0:1)}
L${link.midX} ${link.destY+10*(link.midY<link.startY?1:link.midY==link.startY?0:-1)}
Q${link.midX} ${link.startY} ${link.midX} ${(link.midY<link.startY?Math.max:Math.min)(link.startY+(link.midY<link.startY?-10:link.midY==link.startY?0:10),link.midY)}
L${link.midX} ${(link.midY<link.destY?Math.max:Math.min)(link.destY+(link.midY<link.destY?-10:link.midY==link.destY?0:10),link.midY)}
Q${link.midX} ${link.destY} ${link.destX-10} ${link.destY}
L${link.destX-5} ${link.destY}
L${link.destX-5} ${link.destY-3}
L${link.destX-1} ${link.destY}
L${link.destX-5} ${link.destY+3}
L${link.destX-5} ${link.destY}`
const getCondition = name => {
if (props.model.status == undefined || props.model.status.pipelineSpec == undefined || !Array.isArray(props.model.status.pipelineSpec.tasks)) return null;
const myTasks = (props.model.status.pipelineSpec.tasks.concat(Array.isArray(props.model.status.pipelineSpec.finally)?props.model.status.pipelineSpec.finally:[])).filter(t=>t.name==name)
if (myTasks.length>0 && Array.isArray(myTasks[0].when))
return myTasks[0].when
return null
}
const haveCondition = task => getCondition(task)!=null
const links = computed(() => {
const stgs = stages.value;
const hMargin = stepHMargin.value;
return virtLinks.value.map(link=>{return{...link,
c1: 5,
c2: 5,
destX: getProjectedX(link.dix),
destX: getProjectedX(link.dix)-(haveCondition(link.dst)?4:0),
startX: getProjectedX(link.six)+stepWidth,
destY: getProjectedY(link.diy,stgs[link.col+1].length)+stepHeight/2,
startY: getProjectedY(link.siy,stgs[link.col].length)+stepHeight/2,
Expand All @@ -89,41 +97,65 @@ const links = computed(() => {
const dialogs = ref(Object.fromEntries(Array.isArray(props.model.childtektonTaskRun)?props.model.childtektonTaskRun.map(tr=>[tr.metadata.name,false]):[]))
const GenericView = defineAsyncComponent(() => import( '@/components/generic/GenericView.vue'));
const showDialog = task => {
console.log(task,getTask(task.name))
if(getTask(task.name)!=null){dialogs.value[getTask(task.name).metadata.name]=true}
}
console.log(props.model, stages.value,links.value)
const getConditionClass = task => {
const tsk = getTask(task.name)
if (taskIsSkipped(task.name)) return "isFailed"
if (tsk==null||tsk.status==undefined||!Array.isArray(tsk.status.conditions)||tsk.status.conditions.length<1) return "isPending"
return "isSuccess"
}
</script>
<template><div>
<q-dialog v-for="task in Array.isArray(model.childtektonTaskRun)?model.childtektonTaskRun:[]" :key="task.metadata.name" v-model="dialogs[task.metadata.name]">
<GenericView :model="task" group="tekton" short="TaskRun" :showLabels="false" style="width: 700px; max-width: 80vw;" />
</q-dialog>
<svg ref="svgRoot" :viewBox="[0,0,width,height]" :width="width" :height="height" stroke-linejoin="round" stroke-linecap="round" style="width: 100%; height: auto; font: 10px sans-serif;">
<g class="links" v-for="link in links" :key="`${link.src}-${link.dst}`">
<path :d="link.d" stroke="black" stroke-width="1" fill="none" />
</g>
<g class="rects" v-for="(steps, x) in stages" :key="`rects-${x}`">
<rect v-for="(task, y) in steps" :key="`rect-${task.name}`"
:width="stepWidth" :height="stepHeight" rx="5" ry="5"
:x="getProjectedX(x)" :y="getProjectedY(y, steps.length)"
v-on:click="showDialog(task)"
:class="getClass(task.name)" />
</g>
<g class="labels" text-anchor="middle" v-for="(steps, x) in stages" :key="`labels-${x}`">
<text v-for="(task, y) in steps" :key="`text-${task.name}`"
:x="getProjectedX(x)+stepWidth/2" :y="getProjectedY(y, steps.length)+stepHeight-8"
:class="getClass(task.name)"
v-on:click="showDialog(task)"
>{{ task['name'] }}</text>
</g>
</svg>
<div class="col">
<div class="row" v-if="props.model.spec != undefined && Array.isArray(props.model.spec.params)">
<div v-for="param in props.model.spec.params" :key="param.name" class="col-md-3">
<q-field :label="param.name" stack-label borderless>
<template v-slot:prepend><q-icon name="input" /></template>
<template v-slot:control>
<div class="self-center full-width no-outline">{{ param.value }}</div>
</template>
</q-field>
</div>
</div>
<div>
<svg ref="svgRoot" :viewBox="[0,0,width,height]" :width="width" :height="height" stroke-linejoin="round" stroke-linecap="round" style="width: 100%; height: auto; font: 10px sans-serif;">
<g class="links" v-for="link in links" :key="`${link.src}-${link.dst}`">
<path :d="link.d" stroke="black" stroke-width="1" fill="none" />
</g>
<g class="tasks" v-for="(steps, x) in stages" :key="`rects-${x}`">
<rect v-for="(task, y) in steps" :key="`rect-${task.name}`"
:width="stepWidth" :height="stepHeight" rx="5" ry="5"
:x="getProjectedX(x)" :y="getProjectedY(y, steps.length)"
v-on:click="showDialog(task)"
:class="getClass(task.name)" />
</g>
<g class="labels" text-anchor="middle" v-for="(steps, x) in stages" :key="`labels-${x}`">
<text v-for="(task, y) in steps" :key="`text-${task.name}`"
:x="getProjectedX(x)+stepWidth/2" :y="getProjectedY(y, steps.length)+stepHeight-8"
:class="getClass(task.name)"
v-on:click="showDialog(task)"
>{{ task['name'] }}</text>
</g>
<g class="conditions" v-for="(steps, x) in stages" :key="`conds-${x}`">
<g v-for="(task, y) in steps" :key="`cond-${task.name}`" :transform="`translate(${getProjectedX(x)-3} ${getProjectedY(y, steps.length)+stepHeight/2-3})`">
<rect v-if="haveCondition(task.name)" :transform="`rotate(45 3 3)`" width="6" height="6" :class="getConditionClass(task)" />
</g>
</g>
</svg>
</div>
</div>
</div></template>
<style scoped lang="sass">
@use "quasar/src/css/variables" as q
rect.isFailed
cursor: pointer
rect.isSuccess
cursor: pointer
.tasks
rect.isFailed
cursor: pointer
rect.isSuccess
cursor: pointer
text.isFailed
fill: q.$red
cursor: pointer
Expand Down
29 changes: 24 additions & 5 deletions front/components/tekton/PipelineRunTabLogs.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref,defineAsyncComponent } from 'vue';
const props = defineProps<{
model: object
}>();
const logViewTab = ref((
props.model.childtektonTaskRun != undefined && props.model.childtektonTaskRun.filter(tr=>Array.isArray(tr.childk8sPod)&&tr.childk8sPod.length>0).length>0)?props.model.childtektonTaskRun.filter(tr=>Array.isArray(tr.childk8sPod)&&tr.childk8sPod.length>0).sort((a,b)=>a.metadata.creationTimestamp<b.metadata.creationTimestamp?-1:1)[0].metadata.name
:'none')
const logViewSplitterModel= ref(20)
const getDef = (cont,tr) => {
if (tr.status ==undefined || !Array.isArray(tr.status.steps) || tr.status.taskSpec == undefined || !Array.isArray(tr.status.taskSpec.steps) ) return null
const step = tr.status.steps.filter(s=>s.container==cont.name)
if (step.length<1) return null
const def = tr.status.taskSpec.steps.filter(s=>s.name==step[0].name)
if (def.length<1|| def[0].script==undefined) return null
return def[0]
}
const getSource = (cont,tr) => {
const def = getDef(cont,tr)
return (def!=null&&def!=undefined&&def.script!=undefined)?def.script:""
}
const getLang = script => {
const line = script.split('\n')[0]
if (line.startsWith('#!/') && line.match(/python/)!=null) return "python"
if (line.startsWith('#!/') && line.match(/sh/)!=null) return "shell"
return "text"
}
const MonacoViewer = defineAsyncComponent(() => import( '@/components/core/MonacoViewer.vue'));
</script>
<template>
<q-splitter v-model="logViewSplitterModel" >
Expand All @@ -19,10 +38,10 @@ const logViewSplitterModel= ref(20)
<q-tab-panels v-model="logViewTab" animated swipeable vertical transition-prev="jump-up" transition-next="jump-up">
<q-tab-panel v-for="tr in props.model.childtektonTaskRun.filter(tr=>Array.isArray(tr.childk8sPod)&&tr.childk8sPod.length>0).sort((a,b)=>a.metadata.creationTimestamp<b.metadata.creationTimestamp?-1:1)" v-bind:key="tr.metadata.name" :name="tr.metadata.name" class="bg-black text-white">
<div v-for="cont in tr.childk8sPod.map(po=>po.childcoreContainer.filter(c=>!c.init)).flat()" v-bind:key="cont.name">
<h5>{{ cont.name }}</h5>
<pre v-if="cont.getcoreLog!=undefined && Array.isArray(cont.getcoreLog.lines)">
{{ cont.getcoreLog.lines.join('\n') }}
</pre>
<q-expansion-item icon="code" :label="cont.name">
<MonacoViewer :text="getSource(cont,tr)" :lang="getLang(getSource(cont,tr))" />
</q-expansion-item>
<pre v-if="cont.getcoreLog!=undefined && Array.isArray(cont.getcoreLog.lines)">{{ cont.getcoreLog.lines.join('\n') }}</pre>
</div>
</q-tab-panel>
</q-tab-panels>
Expand Down
1 change: 0 additions & 1 deletion front/components/tekton/TaskMeta.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { computed } from 'vue';
const props = defineProps<{
model: object
}>();
console.log(props.model)
const stepWidth = 100
const stepHeight = 20
const stepHMargin = 10
Expand Down
1 change: 0 additions & 1 deletion front/components/tekton/TaskRunMeta.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { computed } from 'vue';
const props = defineProps<{
model: object
}>();
console.log(props.model)
const stepWidth = 100
const stepHeight = 20
const stepHMargin = 10
Expand Down
32 changes: 32 additions & 0 deletions front/components/tekton/TaskRunTabLogs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { ref,defineAsyncComponent } from 'vue';
const props = defineProps<{
model: object
}>();
const getTaskDef = cont => {
if (props.model.status == undefined || !Array.isArray(props.model.status.steps)) return null
const step = props.model.status.steps.filter(s=>s.container==cont.name)
if (step.length<1) return null
if (props.model.status.taskSpec == undefined || !Array.isArray(props.model.status.taskSpec.steps)) return step[0]
const def = props.model.status.taskSpec.steps.filter(s=>s.name==step[0].name)
if (def.length<1|| def[0].script==undefined) return step[0]
return {...step[0],...def[0]}
}
const getLang = script => {
const line = script.split('\n')[0]
if (line.startsWith('#!/') && line.match(/python/)!=null) return "python"
if (line.startsWith('#!/') && line.match(/sh/)!=null) return "shell"
return "text"
}
const MonacoViewer = defineAsyncComponent(() => import( '@/components/core/MonacoViewer.vue'));
</script>
<template><div>
<div v-for="cont in props.model.childk8sPod.filter(x=>x!=undefined).map(pod=>pod.childcoreContainer.filter(c=>!c.init)).flat().map(cont=>{return {...cont,...getTaskDef(cont)}})" v-bind:key="cont.name">
<q-expansion-item icon="code" :label="cont.name" caption="script" v-if="cont.script!=undefined">
<MonacoViewer :text="cont.script" :lang="getLang(cont.script)" />
</q-expansion-item>
<q-expansion-item icon="output" default-opened :label="cont.name">
<pre class="bg-black text-white" v-if="cont.getcoreLog != undefined && Array.isArray(cont.getcoreLog.lines)">{{ cont.getcoreLog.lines.join('\n') }}</pre>
</q-expansion-item>
</div>
</div></template>
6 changes: 0 additions & 6 deletions utils/generator/partials/front/tekton.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@
}
}
{{/if}}
{{#if (and (eq ../operation 'extraComponents') (eq short 'TaskRun'))}}"TabLogs",{{/if}}
{{#if (and (eq ../operation 'componentLoader') (eq short 'TaskRun'))}}
case "TabLogs":{
return defineAsyncComponent(() => import( '@/components/k8s/ReplicaSetTabLogs.vue'))
}
{{/if}}
{{#if (and (eq ../operation 'componentHave') (eq short 'TaskRun'))}}
(name != 'TabLogs' || Array.isArray(model.childk8sPod)) &&
{{/if}}
Expand Down

0 comments on commit e3aac6b

Please sign in to comment.