Implement catalog entry view and badge redirect

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2021-12-01 00:14:25 +01:00
parent fe63b10e21
commit 230d9211ee
Signed by: luzifer
GPG Key ID: 0066F03ED215AD7D
6 changed files with 256 additions and 26 deletions

35
api.go
View File

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
@ -56,6 +57,40 @@ func catalogEntryToAPICatalogEntry(ce database.CatalogEntry) (APICatalogEntry, e
return APICatalogEntry{CatalogEntry: ce, CatalogMeta: *cm}, nil
}
func handleBadgeRedirect(w http.ResponseWriter, r *http.Request) {
var (
compare = r.FormValue("compare")
vars = mux.Vars(r)
name, tag = vars["name"], vars["tag"]
)
ce, err := configFile.CatalogEntryByTag(name, tag)
if errors.Is(err, config.ErrCatalogEntryNotFound) {
http.Error(w, "Not found", http.StatusNotFound)
return
}
cm, err := storage.Catalog.GetMeta(&ce)
if err != nil {
http.Error(w, "Unable to fetch catalog data", http.StatusInternalServerError)
return
}
color := "green"
if compare != "" && compare != cm.CurrentVersion {
color = "red"
}
target, err := url.Parse(cfg.BadgeGenInstance)
if err != nil {
http.Error(w, "Misconfigured BadgeGenInstance", http.StatusInternalServerError)
return
}
target.Path = path.Join(target.Path, "static", ce.Key(), cm.CurrentVersion, color)
http.Redirect(w, r, target.String(), http.StatusFound)
}
func handleCatalogGet(w http.ResponseWriter, r *http.Request) {
var (
vars = mux.Vars(r)

View File

@ -18,6 +18,7 @@ import (
var (
cfg = struct {
BadgeGenInstance string `flag:"badge-gen-instance" default:"https://badges.fyi/" description:"Where to find the badge-gen instance to use badges from"`
BaseURL string `flag:"base-url" default:"https://example.com/" description:"Base-URL the application is reachable at"`
Config string `flag:"config,c" default:"config.yaml" description:"Configuration file with catalog entries"`
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
@ -84,6 +85,7 @@ func main() {
router.HandleFunc("/v1/catalog/{name}/{tag}/version", handleCatalogGetVersion).Methods(http.MethodGet)
router.HandleFunc("/v1/log", handleLog).Methods(http.MethodGet)
router.HandleFunc("/{name}/{tag}.svg", handleBadgeRedirect).Methods(http.MethodGet).Name("catalog-entry-badge")
router.HandleFunc("/{name}/{tag}/log.rss", handleLogFeed).Methods(http.MethodGet).Name("catalog-entry-rss")
router.HandleFunc("/log.rss", handleLogFeed).Methods(http.MethodGet).Name("log-rss")

View File

@ -35,6 +35,18 @@
/>
All Updates
</b-nav-item>
<b-nav-item
href="https://github.com/Luzifer/go-latestver"
rel="noopener noreferrer"
target="_blank"
title="Go-LatestVer on Github"
>
<font-awesome-icon
fixed-width
:icon="['fab', 'github']"
/>
</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>

155
src/catalog_entry.vue Normal file
View File

@ -0,0 +1,155 @@
<template>
<div>
<b-row>
<b-col>
<b-card-group>
<b-card header="Current Version">
{{ entry.current_version }}<br>
<small>{{ moment(entry.version_time).format('lll') }}</small>
</b-card>
<b-card header="Last Checked">
{{ moment(entry.last_checked).format('lll') }}
</b-card>
<b-card header="Badges">
<p class="text-center">
Current Version:<br>
<img
class="clickable"
:src="badgeURL"
@click="copyURL"
>
</p>
<p class="text-center">
Compare to Version:<br>
<img
class="clickable"
:src="`${badgeURL}?compare=otherversion`"
@click="copyURL"
>
</p>
<p class="text-center">
<small>(Click badge to copy URL)</small>
</p>
</b-card>
<b-card
header="External Links"
no-body
>
<b-list-group flush>
<b-list-group-item
v-for="link in entry.links"
:key="link.name"
:href="link.url"
rel="noopener noreferrer"
target="_blank"
>
<font-awesome-icon
fixed-width
:icon="iconClassesToIcon(link.icon_class)"
/>
{{ link.name }}
</b-list-group-item>
</b-list-group>
</b-card>
</b-card-group>
</b-col>
</b-row>
<b-row class="mt-3">
<b-col>
<log-table :logs="logs" />
</b-col>
</b-row>
</div>
</template>
<script>
import axios from 'axios'
import LogTable from './logtable.vue'
import moment from 'moment'
export default {
components: { LogTable },
computed: {
badgeURL() {
return `${window.location.href.split('?')[0]}.svg`
},
},
data() {
return {
entry: {},
logs: [],
}
},
methods: {
copyURL(evt) {
navigator.clipboard.writeText(evt.target.attributes.src.value)
.then(() => this.$bvToast.toast('URL copied to clipboard', {
autoHideDelay: 2000,
solid: true,
title: 'Copy Badge URL',
variant: 'success',
}))
.catch(() => this.$bvToast.toast('Something went wrong', {
autoHideDelay: 2000,
solid: true,
title: 'Copy Badge URL',
variant: 'danger',
}))
},
fetchEntry() {
axios.get(`/v1/catalog/${this.$route.params.name}/${this.$route.params.tag}`)
.then(resp => {
this.entry = resp.data
})
},
fetchLog() {
axios.get(`/v1/catalog/${this.$route.params.name}/${this.$route.params.tag}/log`)
.then(resp => {
this.logs = resp.data
})
},
iconClassesToIcon(ic) {
let namespace = 'fas'
let icon = ''
for (const c of ic.split(' ')) {
if (c === 'fa-fw') {
continue
}
if (['fab', 'fas'].includes(c)) {
namespace = c
}
if (c.startsWith('fa-')) {
icon = c.replace('fa-', '')
}
}
return [namespace, icon]
},
moment,
},
mounted() {
this.fetchEntry()
this.fetchLog()
},
name: 'GoLatestVerCatalogEntry',
}
</script>
<style>
img.clickable {
cursor:pointer;
}
</style>

View File

@ -1,40 +1,20 @@
<template>
<b-row>
<b-col>
<b-table
:fields="fields"
:items="logs"
small
striped
>
<template #cell(_key)="data">
<router-link :to="{name: 'entry', params: { name:data.item.catalog_name, tag: data.item.catalog_tag }}">
{{ data.item.catalog_name }}:{{ data.item.catalog_tag }}
</router-link>
</template>
<template #cell(timestamp)="data">
{{ moment(data.item.timestamp).format('lll') }}
</template>
</b-table>
<log-table :logs="logs" />
</b-col>
</b-row>
</template>
<script>
import axios from 'axios'
import moment from 'moment'
import LogTable from './logtable.vue'
export default {
components: { LogTable },
data() {
return {
fields: [
{ key: '_key', label: 'Catalog Entry' },
{ key: 'version_from', label: 'Version From' },
{ key: 'version_to', label: 'Version To' },
{ key: 'timestamp', label: 'Updated At' },
],
logs: [],
}
},
@ -46,8 +26,6 @@ export default {
this.logs = resp.data
})
},
moment,
},
mounted() {

48
src/logtable.vue Normal file
View File

@ -0,0 +1,48 @@
<template>
<b-table
:fields="fields"
:items="logs"
small
striped
>
<template #cell(_key)="data">
<router-link :to="{name: 'entry', params: { name:data.item.catalog_name, tag: data.item.catalog_tag }}">
{{ data.item.catalog_name }}:{{ data.item.catalog_tag }}
</router-link>
</template>
<template #cell(timestamp)="data">
{{ moment(data.item.timestamp).format('lll') }}
</template>
</b-table>
</template>
<script>
import moment from 'moment'
export default {
data() {
return {
fields: [
{ key: '_key', label: 'Catalog Entry' },
{ key: 'version_from', label: 'Version From' },
{ key: 'version_to', label: 'Version To' },
{ key: 'timestamp', label: 'Updated At' },
],
}
},
methods: {
moment,
},
name: 'GoLatestVerLogTable',
props: {
logs: {
required: true,
type: Array,
},
},
}
</script>