Skip to content

Commit d58eaf0

Browse files
committed
[ui] Add views for tasks and their jobs
Adds pages to visualize a list of task and a single task. The single task includes a view for a list of its jobs and the detailed view for one job. Signed-off-by: Eva Millán <[email protected]>
1 parent 967f91c commit d58eaf0

30 files changed

+1452
-11
lines changed

ui/.prettierrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
"singleQuote": true,
66
"printWidth": 100,
77
"trailingComma": "none"
8-
}
8+
}

ui/.storybook/preview.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { setup } from '@storybook/vue3';
22
import vuetify from '../src/plugins/vuetify'
3+
import '../src/assets/main.css';
34

45
setup((app) => {
56
app.use(vuetify);

ui/src/App.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup>
2+
import { RouterView } from 'vue-router'
3+
import BreadCrumbs from './components/BreadCrumbs.vue'
24
</script>
35

46
<template>
@@ -8,7 +10,19 @@
810
<img src="./assets/favicon.png" height="30" />
911
</template>
1012
</v-app-bar>
13+
<v-navigation-drawer class="pa-2" color="transparent" permanent>
14+
<v-list color="primary" density="compact">
15+
<v-list-item :to="{ name: 'taskList' }">
16+
<template #prepend>
17+
<v-icon>mdi-calendar</v-icon>
18+
</template>
19+
<v-list-item-title>Tasks</v-list-item-title>
20+
</v-list-item>
21+
</v-list>
22+
</v-navigation-drawer>
1123
<v-main>
24+
<BreadCrumbs />
25+
<RouterView />
1226
</v-main>
1327
</v-app>
1428
</template>

ui/src/assets/cards.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.v-card--variant-outlined {
2+
background: rgb(var(--v-theme-surface));
3+
border: thin solid rgba(0, 0, 0, 0.08);
4+
border-left: 6px solid rgb(var(--v-border-color));
5+
}
6+
7+
.v-chip.v-chip--density-compact {
8+
height: calc(var(--v-chip-height) + -6px);
9+
}

ui/src/assets/main.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
@font-face {
2-
font-family: "Roboto";
3-
src: url("./fonts/Roboto-Medium.woff2") format("woff2");
2+
font-family: 'Roboto';
3+
src: url('./fonts/Roboto-Medium.woff2') format('woff2');
44
font-weight: 500;
55
font-style: normal;
66
}
77

88
@font-face {
9-
font-family: "Roboto";
10-
src: url("./fonts/Roboto-Regular.woff2") format("woff2");
9+
font-family: 'Roboto';
10+
src: url('./fonts/Roboto-Regular.woff2') format('woff2');
1111
font-weight: 400;
1212
font-style: normal;
1313
}

ui/src/components/BreadCrumbs.vue

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<template>
2+
<v-breadcrumbs
3+
:items="breadcrumbs"
4+
class="text-body-2 text-medium-emphasis"
5+
active-class="font-weight-medium text-high-emphasis"
6+
/>
7+
</template>
8+
<script>
9+
export default {
10+
name: 'BreadCrumbs',
11+
computed: {
12+
breadcrumbs() {
13+
return this.$route.matched
14+
.filter((match) => match.meta.breadcrumb)
15+
.map((route) => {
16+
return {
17+
title: this.getTitle(route),
18+
to: route.meta.breadcrumb.to || { name: route.name },
19+
exact: true,
20+
disabled: false
21+
}
22+
})
23+
}
24+
},
25+
methods: {
26+
getTitle(route) {
27+
if (route.meta.breadcrumb.param) {
28+
return `${route.meta.breadcrumb.title} ${this.$route.params[route.meta.breadcrumb.param]}`
29+
} else {
30+
return route.meta.breadcrumb.title
31+
}
32+
}
33+
}
34+
}
35+
</script>
36+
<style lang="scss" scoped>
37+
.v-breadcrumbs {
38+
height: 68px;
39+
40+
:deep(.v-breadcrumbs-item):last-of-type {
41+
font-weight: 500;
42+
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
43+
}
44+
}
45+
</style>

ui/src/components/JobCard.vue

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<template>
2+
<status-card :status="status" class="pa-2">
3+
<v-row>
4+
<v-col cols="6">
5+
<v-card-title class="text-subtitle-2 pb-1">
6+
Job {{ id }}
7+
<v-chip :color="status" class="ml-2" density="comfortable" size="small">
8+
{{ status }}
9+
</v-chip>
10+
</v-card-title>
11+
<p v-if="endedAt" class="px-4 pb-2 text-body-2">
12+
<v-icon color="medium-emphasis" size="small" start> mdi-calendar </v-icon>
13+
{{ formatDate(endedAt) }}
14+
</p>
15+
<p v-if="duration" class="px-4 pb-2 text-body-2">
16+
<v-icon color="medium-emphasis" size="small" start> mdi-alarm </v-icon>
17+
{{ duration }}
18+
</p>
19+
<p v-else-if="status === 'started'" class="px-4 pb-2 text-body-2">
20+
<v-icon color="medium-emphasis" size="small" start> mdi-alarm </v-icon>
21+
In progress
22+
</p>
23+
</v-col>
24+
<v-divider v-if="result" class="mt-2" vertical></v-divider>
25+
<v-col v-if="result" cols="6" class="px-4 py-6">
26+
<p class="text-body-2 mb-2">
27+
Fetched
28+
<span class="font-weight-medium">
29+
{{ result.fetched }}
30+
</span>
31+
</p>
32+
<p class="text-body-2 mb-2">
33+
Skipped
34+
<span class="font-weight-medium">
35+
{{ result.skipped }}
36+
</span>
37+
</p>
38+
</v-col>
39+
</v-row>
40+
</status-card>
41+
</template>
42+
<script>
43+
import { formatDate, getDuration } from '@/utils/dates'
44+
import StatusCard from '@/components/StatusCard.vue'
45+
46+
export default {
47+
name: 'JobCard',
48+
components: { StatusCard },
49+
props: {
50+
id: {
51+
type: String,
52+
required: true
53+
},
54+
status: {
55+
type: String,
56+
required: true
57+
},
58+
result: {
59+
type: Object,
60+
required: false,
61+
default: () => {}
62+
},
63+
startedAt: {
64+
type: String,
65+
required: false,
66+
default: null
67+
},
68+
endedAt: {
69+
type: String,
70+
required: false,
71+
default: null
72+
}
73+
},
74+
computed: {
75+
duration() {
76+
return getDuration(this.startedAt, this.endedAt)
77+
}
78+
},
79+
methods: {
80+
formatDate(date) {
81+
return formatDate(date)
82+
}
83+
}
84+
}
85+
</script>

ui/src/components/JobList.vue

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<template>
2+
<div>
3+
<h2 class="text-h6 mb-4">
4+
Jobs
5+
<v-chip class="ml-2" density="comfortable">
6+
{{ count }}
7+
</v-chip>
8+
</h2>
9+
<status-card
10+
v-for="job in jobs"
11+
:key="job.uuid"
12+
:status="job.status"
13+
:to="{
14+
name: 'job',
15+
params: { jobid: job.uuid }
16+
}"
17+
class="mb-2"
18+
>
19+
<v-row>
20+
<v-col>
21+
<v-card-title class="text-subtitle-2 pb-0">
22+
{{ job.uuid }}
23+
<v-chip :color="job.status" class="ml-3" density="comfortable" size="small">
24+
{{ job.status }}
25+
</v-chip>
26+
</v-card-title>
27+
<v-card-subtitle class="pb-2"> #{{ job.job_num }} </v-card-subtitle>
28+
</v-col>
29+
<v-col cols="4">
30+
<p v-if="job.finished_at" class="px-4 pt-2 text-body-2">
31+
<v-icon color="medium-emphasis" size="small" start> mdi-calendar </v-icon>
32+
{{ formatDate(job.finished_at) }}
33+
</p>
34+
<p v-if="job.finished_at" class="px-4 py-2 text-body-2">
35+
<v-icon color="medium-emphasis" size="small" start> mdi-alarm </v-icon>
36+
{{ getDuration(job.scheduled_at, job.finished_at) }}
37+
</p>
38+
<p v-else-if="job.status === 'started'" class="px-4 py-2 text-body-2">
39+
<v-icon color="medium-emphasis" size="small" start> mdi-alarm </v-icon>
40+
In progress
41+
</p>
42+
</v-col>
43+
</v-row>
44+
</status-card>
45+
<v-pagination
46+
v-model="page"
47+
:length="pages"
48+
color="primary"
49+
density="comfortable"
50+
@update:model-value="$emit('update:page', $event)"
51+
/>
52+
</div>
53+
</template>
54+
<script>
55+
import { formatDate, getDuration } from '@/utils/dates'
56+
import StatusCard from '@/components/StatusCard.vue'
57+
58+
export default {
59+
name: 'JobList',
60+
components: { StatusCard },
61+
emits: ['update:page'],
62+
props: {
63+
jobs: {
64+
type: Array,
65+
required: true
66+
},
67+
count: {
68+
type: Number,
69+
required: true
70+
},
71+
pages: {
72+
type: Number,
73+
required: true
74+
}
75+
},
76+
data() {
77+
return {
78+
page: 1
79+
}
80+
},
81+
methods: {
82+
formatDate,
83+
getDuration
84+
}
85+
}
86+
</script>
87+
<style lang="scss" scoped>
88+
.v-card .v-card-title {
89+
line-height: 1.7rem;
90+
}
91+
</style>

ui/src/components/LogContainer.vue

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<template>
2+
<code>
3+
<p v-for="log in logs" :key="log.created">
4+
{{ formattedDate(log) }} [{{ log.module }}] {{ log.msg }}
5+
</p>
6+
</code>
7+
</template>
8+
<script>
9+
import { formatDate } from '@/utils/dates'
10+
11+
export default {
12+
name: 'LogContainer',
13+
props: {
14+
logs: {
15+
type: Array,
16+
required: true
17+
}
18+
},
19+
methods: {
20+
formattedDate(log) {
21+
return formatDate(log.created * 1000)
22+
}
23+
}
24+
}
25+
</script>
26+
<style lang="scss" scoped>
27+
code {
28+
display: block;
29+
background-color: rgb(var(--v-theme-on-surface));
30+
color: rgb(var(--v-theme-surface));
31+
padding: 1rem;
32+
border-radius: 4px;
33+
font-size: 0.8rem;
34+
white-space: pre-wrap;
35+
36+
p {
37+
padding: 4px;
38+
}
39+
}
40+
</style>

ui/src/components/StatusCard.vue

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<v-card :class="borderClass" variant="outlined">
3+
<slot></slot>
4+
</v-card>
5+
</template>
6+
<script>
7+
export default {
8+
name: 'StatusCard',
9+
props: {
10+
status: {
11+
type: String,
12+
required: true
13+
}
14+
},
15+
computed: {
16+
borderClass() {
17+
return `border-${this.status?.toLowerCase()}`
18+
}
19+
}
20+
}
21+
</script>
22+
<style lang="scss" scoped>
23+
.v-card--variant-outlined {
24+
background: rgb(var(--v-theme-surface));
25+
border: thin solid rgba(0, 0, 0, 0.08);
26+
border-left: 6px solid rgb(var(--v-border-color));
27+
}
28+
</style>

0 commit comments

Comments
 (0)