Skip to content

Commit eaa07aa

Browse files
committed
feat(ui): add a news feeds
1 parent 73168c4 commit eaa07aa

File tree

11 files changed

+260
-21
lines changed

11 files changed

+260
-21
lines changed

Diff for: core/src/main/java/io/kestra/core/services/CollectorService.java

+4-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package io.kestra.core.services;
22

33
import io.kestra.core.models.ServerType;
4-
import io.kestra.core.models.Setting;
54
import io.kestra.core.models.collectors.*;
65
import io.kestra.core.repositories.ExecutionRepositoryInterface;
76
import io.kestra.core.repositories.FlowRepositoryInterface;
8-
import io.kestra.core.repositories.SettingRepositoryInterface;
97
import io.kestra.core.serializers.JacksonMapper;
108
import io.kestra.core.utils.IdUtils;
119
import io.kestra.core.utils.VersionProvider;
@@ -19,14 +17,14 @@
1917
import io.micronaut.http.client.exceptions.HttpClientResponseException;
2018
import io.micronaut.http.hateoas.JsonError;
2119
import io.micronaut.rxjava2.http.client.RxHttpClient;
20+
import jakarta.inject.Inject;
21+
import jakarta.inject.Singleton;
2222
import lombok.extern.slf4j.Slf4j;
2323

2424
import java.lang.management.ManagementFactory;
2525
import java.net.URI;
2626
import java.time.Instant;
2727
import java.time.ZoneId;
28-
import jakarta.inject.Inject;
29-
import jakarta.inject.Singleton;
3028

3129
@Singleton
3230
@Slf4j
@@ -47,7 +45,7 @@ public class CollectorService {
4745
private ExecutionRepositoryInterface executionRepository;
4846

4947
@Inject
50-
private SettingRepositoryInterface settingRepository;
48+
private InstanceService instanceService;
5149

5250
@Inject
5351
private VersionProvider versionProvider;
@@ -69,17 +67,9 @@ protected synchronized Usage defaultUsage() {
6967
boolean first = defaultUsage == null;
7068

7169
if (first) {
72-
Setting instanceIdSetting = settingRepository
73-
.findByKey(Setting.INSTANCE_UUID)
74-
.orElseGet(() -> settingRepository.save(Setting.builder()
75-
.key(Setting.INSTANCE_UUID)
76-
.value(IdUtils.create())
77-
.build()
78-
));
79-
8070
defaultUsage = Usage.builder()
8171
.startUuid(UUID)
82-
.instanceUuid(instanceIdSetting.getValue().toString())
72+
.instanceUuid(instanceService.fetch())
8373
.serverType(serverType)
8474
.version(versionProvider.getVersion())
8575
.zoneId(ZoneId.systemDefault())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.kestra.core.services;
2+
3+
import io.kestra.core.models.Setting;
4+
import io.kestra.core.repositories.SettingRepositoryInterface;
5+
import io.kestra.core.utils.IdUtils;
6+
import jakarta.inject.Inject;
7+
import jakarta.inject.Singleton;
8+
import lombok.extern.slf4j.Slf4j;
9+
10+
@Singleton
11+
@Slf4j
12+
public class InstanceService {
13+
@Inject
14+
private SettingRepositoryInterface settingRepository;
15+
16+
private Setting instanceIdSetting;
17+
18+
public String fetch() {
19+
if (this.instanceIdSetting == null) {
20+
instanceIdSetting = settingRepository
21+
.findByKey(Setting.INSTANCE_UUID)
22+
.orElseGet(() -> settingRepository.save(Setting.builder()
23+
.key(Setting.INSTANCE_UUID)
24+
.value(IdUtils.create())
25+
.build()
26+
));
27+
}
28+
29+
return this.instanceIdSetting.getValue().toString();
30+
}
31+
}

Diff for: ui/src/App.vue

+14
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import NprogressContainer from "vue-nprogress/src/NprogressContainer";
2424
import Errors from "./components/errors/Errors";
2525
import {mapState} from "vuex";
26+
import Utils from "./utils/utils";
2627
2728
export default {
2829
name: "App",
@@ -71,8 +72,21 @@
7172
});
7273
},
7374
loadGeneralRessources() {
75+
let uid = localStorage.getItem("uid");
76+
if (uid === null) {
77+
uid = Utils.uid();
78+
localStorage.setItem("uid", uid);
79+
}
80+
7481
this.$store.dispatch("plugin/icons")
7582
this.$store.dispatch("misc/loadConfigs")
83+
.then(value => {
84+
this.$store.dispatch("api/loadFeeds", {
85+
version: value.version,
86+
iid: value.uuid,
87+
uid: uid,
88+
});
89+
})
7690
},
7791
grabThemeResources() {
7892
// eslint-disable-next-line no-undef

Diff for: ui/src/components/layout/DateAgo.vue

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313
inverted: {
1414
type: Boolean,
1515
default: false
16+
},
17+
format: {
18+
type: String,
19+
default: "LLLL"
1620
}
1721
},
1822
computed: {
1923
from() {
2024
return Vue.moment(this.date).fromNow();
2125
},
2226
full() {
23-
return Vue.moment(this.date).format("LLLL");
27+
return Vue.moment(this.date).format(this.format);
2428
}
2529
}
2630
};

Diff for: ui/src/components/layout/News.vue

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<template>
2+
<b-nav-item>
3+
<a class="news-link" v-b-modal="`news-modal`">
4+
<gift title="" />
5+
<CheckboxBlankCircle v-if="hasUnread" class="new" title="" />
6+
</a>
7+
8+
<b-modal
9+
id="news-modal"
10+
:title="$t('feeds.title')"
11+
hide-backdrop
12+
hide-footer
13+
modal-class="right"
14+
size="xl"
15+
@show="show"
16+
>
17+
<div class="post" v-for="(feed, index) in feeds" :key="feed.id">
18+
<div v-if="feed.image" class="mt-2">
19+
<img class="float-right" :src="feed.image" alt="">
20+
</div>
21+
<h5>
22+
{{ feed.title }}
23+
</h5>
24+
<date-ago class="text-muted small" :inverted="true" :date="feed.publicationDate" format="LL" />
25+
26+
27+
<markdown class="markdown-tooltip mt-3" :source="feed.description" />
28+
29+
<a class="mt-3 d-block text-right" :href="feed.href" target="_blank">{{ feed.link }} <OpenInNew /></a>
30+
31+
<hr v-if="index !== feeds.length - 1" class="text-white">
32+
</div>
33+
</b-modal>
34+
</b-nav-item>
35+
</template>
36+
37+
<script>
38+
import {mapState} from "vuex";
39+
import Gift from "vue-material-design-icons/Gift";
40+
import OpenInNew from "vue-material-design-icons/OpenInNew";
41+
import CheckboxBlankCircle from "vue-material-design-icons/CheckboxBlankCircle";
42+
import Markdown from "./Markdown";
43+
import DateAgo from "./DateAgo";
44+
45+
export default {
46+
components: {
47+
Gift,
48+
OpenInNew,
49+
CheckboxBlankCircle,
50+
Markdown,
51+
DateAgo
52+
},
53+
data() {
54+
return {
55+
hasUnread: false,
56+
};
57+
},
58+
mounted() {
59+
this.hasUnread = this.isUnread();
60+
},
61+
watch: {
62+
feeds() {
63+
this.hasUnread = this.isUnread();
64+
}
65+
},
66+
methods: {
67+
show() {
68+
localStorage.setItem("feeds", this.feeds[0].publicationDate)
69+
this.hasUnread = this.isUnread();
70+
},
71+
isUnread() {
72+
let storage = localStorage.getItem("feeds");
73+
return (
74+
storage === null ||
75+
(this.feeds && this.feeds[0] && this.$moment(storage).isBefore(this.feeds[0].publicationDate))
76+
);
77+
},
78+
},
79+
computed: {
80+
...mapState("misc", ["configs"]),
81+
...mapState("api", ["feeds"]),
82+
83+
84+
}
85+
};
86+
</script>
87+
88+
<style lang="scss" scoped>
89+
@import "../../styles/variable";
90+
.news-link {
91+
font-size: $font-size-lg;
92+
color: var(--gray-600);
93+
}
94+
95+
.new {
96+
font-size: $font-size-xs;
97+
color: $red;
98+
position: absolute;
99+
margin-left: -8px;
100+
101+
animation-name: grow;
102+
animation-duration: 1.5s;
103+
animation-iteration-count: infinite;
104+
animation-timing-function: ease-in;
105+
}
106+
107+
@keyframes grow {
108+
0% {
109+
transform: scale(0.8);
110+
}
111+
50% {
112+
transform: scale(1.2);
113+
}
114+
100% {
115+
transform: scale(0.8);
116+
}
117+
}
118+
119+
.post {
120+
h5 {
121+
font-weight: bold;
122+
margin-bottom: 0;
123+
}
124+
125+
img {
126+
max-height: 120px;
127+
max-width: 240px;
128+
padding-left: 20px;
129+
padding-bottom: 20px;
130+
}
131+
132+
hr {
133+
border-top-color: var(--gray-700);
134+
margin-top: $spacer * 2;
135+
margin-bottom: $spacer * 2;
136+
}
137+
138+
a.d-block {
139+
font-weight: bold;
140+
}
141+
}
142+
143+
</style>

Diff for: ui/src/components/layout/TopNavBar.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@
1515
</b-breadcrumb>
1616
</b-nav-text>
1717
</b-navbar-nav>
18-
<Auth />
18+
<b-navbar-nav class="ml-auto pt-3">
19+
<Auth />
20+
<news />
21+
</b-navbar-nav>
1922
</b-navbar>
2023
</template>
2124
<script>
2225
import {mapState} from "vuex";
2326
import HomeOutline from "vue-material-design-icons/HomeOutline";
2427
import Auth from "override/components/auth/Auth";
28+
import News from "../layout/News";
2529
2630
export default {
2731
components: {
2832
HomeOutline,
2933
Auth,
34+
News,
3035
},
3136
props: {
3237
menuCollapsed : {

Diff for: ui/src/stores/api.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Vue from "vue"
2+
3+
export default {
4+
namespaced: true,
5+
state: {
6+
feeds: [],
7+
},
8+
9+
actions: {
10+
loadFeeds({commit}, options) {
11+
return Vue.axios.get("https://api.kestra.io/v1/feeds/latest", {
12+
params: {
13+
iid: options.iid,
14+
uid: options.uid,
15+
version: options.version
16+
}
17+
}).then(response => {
18+
commit("setFeeds", response.data)
19+
20+
return response.data;
21+
})
22+
}
23+
},
24+
mutations: {
25+
setFeeds(state, feeds) {
26+
state.feeds = feeds
27+
}
28+
}
29+
}

Diff for: ui/src/stores/store.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import api from "./api"
12
import auth from "./auth"
23
import core from "./core"
34
import execution from "./executions"
@@ -15,6 +16,7 @@ import taskrun from "./taskruns"
1516

1617
export default {
1718
modules: {
19+
api,
1820
core,
1921
settings,
2022
flow,

Diff for: ui/src/styles/layout/bootstrap.scss

+10
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,14 @@ ul.nav-tabs {
678678
font-weight: bold;
679679
}
680680
}
681+
682+
.modal-content {
683+
border: 1px solid var(--gray-300);
684+
}
685+
686+
.modal-header {
687+
border-bottom: 1px solid var(--gray-300);
688+
}
681689
}
682690

683691
.modal.left,
@@ -694,6 +702,8 @@ ul.nav-tabs {
694702
height: 100%;
695703
border-radius: 0;
696704
overflow-y: auto;
705+
border-bottom: 0;
706+
border-top: 0;
697707

698708
header {
699709
border-radius: 0;

Diff for: ui/src/translations.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,10 @@
235235
"tags": "Tags",
236236
"disabled flow title": "This flow is disabled",
237237
"disabled flow desc": "This flow is disabled, please enable it in order to execute it.",
238-
"labels": "Labels"
238+
"labels": "Labels",
239+
"feeds": {
240+
"title": "What's new at Kestra"
241+
}
239242
},
240243
"fr": {
241244
"id": "Identifiant",
@@ -474,6 +477,9 @@
474477
"tags": "Tags",
475478
"disabled flow title": "Ce flux est désactivé",
476479
"disabled flow desc": "Ce flux est désactivé, veuillez le réactiver pour l'exécuter.",
477-
"labels": "Labels"
480+
"labels": "Labels",
481+
"feeds": {
482+
"title": "Quoi de neuf sur Kestra"
483+
}
478484
}
479485
}

0 commit comments

Comments
 (0)