mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 13:17:08 +00:00
Show development activity log.
A button in the help dialogue takes the user to the /feed/… frontend URL, where the latest development activity is shown, taken from the GitLab RSS feed for the project.
This commit is contained in:
@@ -45,6 +45,14 @@
|
||||
<v-icon class="d-lg-none">mdi-bug</v-icon>
|
||||
<span class="d-none d-lg-inline">Report a bug</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="info"
|
||||
text
|
||||
:href='"/feed/"+feed'
|
||||
title="View development log"
|
||||
>
|
||||
<v-icon>mdi-rss</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
@@ -73,7 +81,8 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
dialog: false,
|
||||
email: "dougal-support@aaltronav.eu"
|
||||
email: "dougal-support@aaltronav.eu",
|
||||
feed: btoa(encodeURIComponent("https://gitlab.com/wgp/dougal/software.atom?feed_token=XSPpvsYEny8YmH75Nz5W"))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,14 @@ Vue.use(VueRouter)
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
|
||||
},
|
||||
{
|
||||
path: '/feed/:source',
|
||||
name: 'Feed',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import(/* webpackChunkName: "about" */ '../views/Feed.vue')
|
||||
},
|
||||
{
|
||||
pathToRegexpOptions: { strict: true },
|
||||
path: "/login",
|
||||
|
||||
103
lib/www/client/source/src/views/Feed.vue
Normal file
103
lib/www/client/source/src/views/Feed.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-overlay absolute opacity="1" :value="!feed.updated">
|
||||
<v-progress-circular indeterminate size="64"></v-progress-circular>
|
||||
</v-overlay>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{feed.title}}
|
||||
<a :href="feed.link"><v-icon class="ml-2">mdi-link</v-icon></a>
|
||||
</v-card-title>
|
||||
<v-card-subtitle>Last updated: {{feed.updated}}</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-timeline align-top dense>
|
||||
<v-timeline-item v-for="item in feed.items" small>
|
||||
<v-card :color="item.colour">
|
||||
<v-card-title primary-title>
|
||||
<div class="headline">{{item.title}}</div>
|
||||
</v-card-title>
|
||||
<v-card-subtitle>{{item.updated}} ({{item.author}})</v-card-subtitle>
|
||||
<v-card-text v-html="item.summary">
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn target="_new" :href="item.link">Complete story</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "FeedViewer",
|
||||
|
||||
data () {
|
||||
return {
|
||||
timer: null,
|
||||
feed: {}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
parse (text) {
|
||||
const data = {items:[]};
|
||||
const parser = new DOMParser();
|
||||
const xml = parser.parseFromString(text, "application/xml");
|
||||
const feed = xml.getElementsByTagNameNS("http://www.w3.org/2005/Atom", "feed")[0];
|
||||
const entries = feed.getElementsByTagName("entry");
|
||||
|
||||
data.title = feed.getElementsByTagName("title")[0].childNodes[0].textContent;
|
||||
data.updated = feed.getElementsByTagName("updated")[0].childNodes[0].textContent;
|
||||
data.link = [...feed.getElementsByTagName("link")].filter(i =>
|
||||
i.getAttribute("type") == "text/html"
|
||||
).pop().getAttribute("href");
|
||||
|
||||
data.items = [...entries].map(entry => {
|
||||
const item = {};
|
||||
const link = entry.getElementsByTagName("link")[0];
|
||||
if (link) {
|
||||
item.link = link.getAttribute("href");
|
||||
}
|
||||
const author = entry.getElementsByTagName("author")[0];
|
||||
if (author) {
|
||||
const name = author.getElementsByTagName("name")[0];
|
||||
item.author = name ? name.childNodes[0].textContent : author.innerHTML;
|
||||
}
|
||||
const summaries = entry.getElementsByTagName("summary");
|
||||
const summary = [...summaries].find(i => i.getAttribute("type") == "xhtml") || summaries[0];
|
||||
|
||||
item.summary = summary.innerHTML;
|
||||
item.title = entry.getElementsByTagName("title")[0].childNodes[0].textContent;
|
||||
item.updated = entry.getElementsByTagName("updated")[0].childNodes[0].textContent;
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
async refresh () {
|
||||
const text = await this.api([`/rss/?remote=${atob(this.$route.params.source)}`, {text:true}]);
|
||||
this.feed = this.parse(text);
|
||||
},
|
||||
|
||||
...mapActions(["api"])
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
await this.refresh();
|
||||
this.timer = setInterval(this.refresh, 300000);
|
||||
},
|
||||
|
||||
unmounted () {
|
||||
cancelInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -183,6 +183,9 @@ app.map({
|
||||
get: [ mw.gis.navdata.get ]
|
||||
}
|
||||
},
|
||||
'/rss/': {
|
||||
get: [ mw.rss.get ]
|
||||
}
|
||||
//
|
||||
// '/user': {
|
||||
// get: [ mw.user.get ],
|
||||
|
||||
@@ -12,5 +12,6 @@ module.exports = {
|
||||
configuration: require('./configuration'),
|
||||
info: require('./info'),
|
||||
meta: require('./meta'),
|
||||
openapi: require('./openapi')
|
||||
openapi: require('./openapi'),
|
||||
rss: require('./rss')
|
||||
};
|
||||
|
||||
21
lib/www/server/api/middleware/rss/get.js
Normal file
21
lib/www/server/api/middleware/rss/get.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
module.exports = async function (req, res, next) {
|
||||
|
||||
try {
|
||||
if (req.query.remote) {
|
||||
// We're being asked to fetch a remote feed
|
||||
// NOTE: No, we don't limit what feeds the user can fetch
|
||||
|
||||
const r = await fetch(req.query.remote);
|
||||
if (r && r.ok) {
|
||||
res.set("Content-Type", "application/xml");
|
||||
res.send(await r.text());
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.status(400).send();
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
4
lib/www/server/api/middleware/rss/index.js
Normal file
4
lib/www/server/api/middleware/rss/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
module.exports = {
|
||||
get: require('./get')
|
||||
}
|
||||
Reference in New Issue
Block a user