mirror of
https://github.com/Luzifer/go-latestver.git
synced 2024-11-09 23:50:05 +00:00
Implement catalog entry view and badge redirect
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
fe63b10e21
commit
230d9211ee
6 changed files with 256 additions and 26 deletions
35
api.go
35
api.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -56,6 +57,40 @@ func catalogEntryToAPICatalogEntry(ce database.CatalogEntry) (APICatalogEntry, e
|
||||||
return APICatalogEntry{CatalogEntry: ce, CatalogMeta: *cm}, nil
|
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) {
|
func handleCatalogGet(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
vars = mux.Vars(r)
|
vars = mux.Vars(r)
|
||||||
|
|
2
main.go
2
main.go
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg = struct {
|
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"`
|
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"`
|
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"`
|
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/catalog/{name}/{tag}/version", handleCatalogGetVersion).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/v1/log", handleLog).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("/{name}/{tag}/log.rss", handleLogFeed).Methods(http.MethodGet).Name("catalog-entry-rss")
|
||||||
router.HandleFunc("/log.rss", handleLogFeed).Methods(http.MethodGet).Name("log-rss")
|
router.HandleFunc("/log.rss", handleLogFeed).Methods(http.MethodGet).Name("log-rss")
|
||||||
|
|
||||||
|
|
12
src/app.vue
12
src/app.vue
|
@ -35,6 +35,18 @@
|
||||||
/>
|
/>
|
||||||
All Updates
|
All Updates
|
||||||
</b-nav-item>
|
</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-navbar-nav>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
</b-navbar>
|
</b-navbar>
|
||||||
|
|
155
src/catalog_entry.vue
Normal file
155
src/catalog_entry.vue
Normal 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>
|
30
src/log.vue
30
src/log.vue
|
@ -1,40 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col>
|
<b-col>
|
||||||
<b-table
|
<log-table :logs="logs" />
|
||||||
: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>
|
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import moment from 'moment'
|
import LogTable from './logtable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { LogTable },
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
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: [],
|
logs: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -46,8 +26,6 @@ export default {
|
||||||
this.logs = resp.data
|
this.logs = resp.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
moment,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
48
src/logtable.vue
Normal file
48
src/logtable.vue
Normal 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>
|
Loading…
Reference in a new issue